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
    18  package org.apache.logging.log4j.core.config.status;
    19
    20  import java.io.File;
    21  import java.io.FileNotFoundException;
    22  import java.io.FileOutputStream;
    23  import java.io.PrintStream;
    24  import java.net.URI;
    25  import java.net.URISyntaxException;
    26  import java.util.Collection;
    27  import java.util.Collections;
    28  import java.util.LinkedList;
    29
    30  import org.apache.logging.log4j.Level;
    31  import org.apache.logging.log4j.core.util.FileUtils;
    32  import org.apache.logging.log4j.status.StatusConsoleListener;
    33  import org.apache.logging.log4j.status.StatusListener;
    34  import org.apache.logging.log4j.status.StatusLogger;
    35
    36  /**
    37   * Configuration for setting up {@link StatusConsoleListener} instances.
    38   */
    39  public class StatusConfiguration {
    40
    41      @SuppressWarnings("UseOfSystemOutOrSystemErr")
    42      private static final PrintStream DEFAULT_STREAM = System.out;
    43      private static final Level DEFAULT_STATUS = Level.ERROR;
    44      private static final Verbosity DEFAULT_VERBOSITY = Verbosity.QUIET;
    45
    46      private final Collection<String> errorMessages = Collections.synchronizedCollection(new LinkedList<String>());
    47      private final StatusLogger logger = StatusLogger.getLogger();
    48
    49      private volatile boolean initialized = false;
    50
    51      private PrintStream destination = DEFAULT_STREAM;
    52      private Level status = DEFAULT_STATUS;
    53      private Verbosity verbosity = DEFAULT_VERBOSITY;
    54      private String[] verboseClasses;
    55
    56      /**
    57       * Specifies how verbose the StatusLogger should be.
    58       */
    59      public static enum Verbosity {
    60          QUIET, VERBOSE;
    61
    62          /**
    63           * Parses the verbosity property into an enum.
    64           *
    65           * @param value property value to parse.
    66           * @return enum corresponding to value, or QUIET by default.
    67           */
    68          public static Verbosity toVerbosity(final String value) {
    69              return Boolean.parseBoolean(value) ? VERBOSE : QUIET;
    70          }
    71      }
    72
    73      /**
    74       * Logs an error message to the StatusLogger. If the StatusLogger hasn't been set up yet, queues the message to be
    75       * logged after initialization.
    76       *
    77       * @param message error message to log.
    78       */
    79      public void error(final String message) {
    80          if (!this.initialized) {
    81              this.errorMessages.add(message);
    82          } else {
    83              this.logger.error(message);
    84          }
    85      }
    86
    87      /**
    88       * Specifies the destination for StatusLogger events. This can be {@code out} (default) for using
    89       * {@link System#out standard out}, {@code err} for using {@link System#err standard error}, or a file URI to
    90       * which log events will be written. If the provided URI is invalid, then the default destination of standard
    91       * out will be used.
    92       *
    93       * @param destination where status log messages should be output.
    94       * @return {@code this}
    95       */
    96      public StatusConfiguration withDestination(final String destination) {
    97          try {
    98              this.destination = parseStreamName(destination);
    99          } catch (final URISyntaxException e) {
    100             this.error("Could not parse URI [" + destination + "]. Falling back to default of stdout.");
    101             this.destination = DEFAULT_STREAM;
    102         } catch (final FileNotFoundException e) {
    103             this.error("File could not be found at [" + destination + "]. Falling back to default of stdout.");
    104             this.destination = DEFAULT_STREAM;
    105         }
    106         return this;
    107     }
    108
    109     private PrintStream parseStreamName(final String name) throws URISyntaxException, FileNotFoundException {
    110         if (name == null || name.equalsIgnoreCase("out")) {
    111             return DEFAULT_STREAM;
    112         }
    113         if (name.equalsIgnoreCase("err")) {
    114             return System.err;
    115         }
    116         final URI destination = FileUtils.getCorrectedFilePathUri(name);
    117         final File output = FileUtils.fileFromUri(destination);
    118         if (output == null) {
    119             // don't want any NPEs, no sir
    120             return DEFAULT_STREAM;
    121         }
    122         final FileOutputStream fos = new FileOutputStream(output);
    123         return new PrintStream(fos, true);
    124     }
    125
    126     /**
    127      * Specifies the logging level by name to use for filtering StatusLogger messages.
    128      *
    129      * @param status name of logger level to filter below.
    130      * @return {@code this}
    131      * @see Level
    132      */
    133     public StatusConfiguration withStatus(final String status) {
    134         this.status = Level.toLevel(status, null);
    135         if (this.status == null) {
    136             this.error("Invalid status level specified: " + status + ". Defaulting to ERROR.");
    137             this.status = Level.ERROR;
    138         }
    139         return this;
    140     }
    141
    142     /**
    143      * Specifies the logging level to use for filtering StatusLogger messages.
    144      *
    145      * @param status logger level to filter below.
    146      * @return {@code this}
    147      */
    148     public StatusConfiguration withStatus(final Level status) {
    149         this.status = status;
    150         return this;
    151     }
    152
    153     /**
    154      * Specifies the verbosity level to log at. This only applies to classes configured by
    155      * {@link #withVerboseClasses(String...) verboseClasses}.
    156      *
    157      * @param verbosity basic filter for status logger messages.
    158      * @return {@code this}
    159      */
    160     public StatusConfiguration withVerbosity(final String verbosity) {
    161         this.verbosity = Verbosity.toVerbosity(verbosity);
    162         return this;
    163     }
    164
    165     /**
    166      * Specifies which class names to filter if the configured verbosity level is QUIET.
    167      *
    168      * @param verboseClasses names of classes to filter if not using VERBOSE.
    169      * @return {@code this}
    170      */
    171     public StatusConfiguration withVerboseClasses(final String... verboseClasses) {
    172         this.verboseClasses = verboseClasses;
    173         return this;
    174     }
    175
    176     /**
    177      * Configures and initializes the StatusLogger using the configured options in this instance.
    178      */
    179     public void initialize() {
    180         if (!this.initialized) {
    181             if (this.status == Level.OFF) {
    182                 this.initialized = true;
    183             } else {
    184                 final boolean configured = configureExistingStatusConsoleListener();
    185                 if (!configured) {
    186                     registerNewStatusConsoleListener();
    187                 }
    188                 migrateSavedLogMessages();
    189             }
    190         }
    191     }
    192
    193     private boolean configureExistingStatusConsoleListener() {
    194         boolean configured = false;
    195         for (final StatusListener statusListener : this.logger.getListeners()) {
    196             if (statusListener instanceof StatusConsoleListener) {
    197                 final StatusConsoleListener listener = (StatusConsoleListener) statusListener;
    198                 listener.setLevel(this.status);
    199                 if (this.verbosity == Verbosity.QUIET) {
    200                     listener.setFilters(this.verboseClasses);
    201                 }
    202                 configured = true;
    203             }
    204         }
    205         return configured;
    206     }
    207
    208
    209     private void registerNewStatusConsoleListener() {
    210         final StatusConsoleListener listener = new StatusConsoleListener(this.status, this.destination);
    211         if (this.verbosity == Verbosity.QUIET) {
    212             listener.setFilters(this.verboseClasses);
    213         }
    214         this.logger.registerListener(listener);
    215     }
    216
    217     private void migrateSavedLogMessages() {
    218         for (final String message : this.errorMessages) {
    219             this.logger.error(message);
    220         }
    221         this.initialized = true;
    222         this.errorMessages.clear();
    223     }
    224 }