    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     */
    017
    018    package org.apache.logging.log4j.core.config.status;
    019
    020    import java.io.File;
    021    import java.io.FileNotFoundException;
    022    import java.io.FileOutputStream;
    023    import java.io.PrintStream;
    024    import java.net.URI;
    025    import java.net.URISyntaxException;
    026    import java.util.Collection;
    027    import java.util.Collections;
    028    import java.util.LinkedList;
    029
    030    import org.apache.logging.log4j.Level;
    031    import org.apache.logging.log4j.core.util.FileUtils;
    032    import org.apache.logging.log4j.status.StatusConsoleListener;
    033    import org.apache.logging.log4j.status.StatusListener;
    034    import org.apache.logging.log4j.status.StatusLogger;
    035
    036    /**
    037     * Configuration for setting up {@link StatusConsoleListener} instances.
    038     */
    039    public class StatusConfiguration {
    040
    041        @SuppressWarnings("UseOfSystemOutOrSystemErr")
    042        private static final PrintStream DEFAULT_STREAM = System.out;
    043        private static final Level DEFAULT_STATUS = Level.ERROR;
    044        private static final Verbosity DEFAULT_VERBOSITY = Verbosity.QUIET;
    045
    046        private final Collection<String> errorMessages = Collections.synchronizedCollection(new LinkedList<String>());
    047        private final StatusLogger logger = StatusLogger.getLogger();
    048
    049        private volatile boolean initialized = false;
    050
    051        private PrintStream destination = DEFAULT_STREAM;
    052        private Level status = DEFAULT_STATUS;
    053        private Verbosity verbosity = DEFAULT_VERBOSITY;
    054        private String[] verboseClasses;
    055
    056        /**
    057         * Specifies how verbose the StatusLogger should be.
    058         */
    059        public static enum Verbosity {
    060            QUIET, VERBOSE;
    061
    062            /**
    063             * Parses the verbosity property into an enum.
    064             *
    065             * @param value property value to parse.
    066             * @return enum corresponding to value, or QUIET by default.
    067             */
    068            public static Verbosity toVerbosity(final String value) {
    069                return Boolean.parseBoolean(value) ? VERBOSE : QUIET;
    070            }
    071        }
    072
    073        /**
    074         * Logs an error message to the StatusLogger. If the StatusLogger hasn't been set up yet, queues the message to be
    075         * logged after initialization.
    076         *
    077         * @param message error message to log.
    078         */
    079        public void error(final String message) {
    080            if (!this.initialized) {
    081                this.errorMessages.add(message);
    082            } else {
    083                this.logger.error(message);
    084            }
    085        }
    086
    087        /**
    088         * Specifies the destination for StatusLogger events. This can be {@code out} (default) for using
    089         * {@link System#out standard out}, {@code err} for using {@link System#err standard error}, or a file URI to
    090         * which log events will be written. If the provided URI is invalid, then the default destination of standard
    091         * out will be used.
    092         *
    093         * @param destination where status log messages should be output.
    094         * @return {@code this}
    095         */
    096        public StatusConfiguration withDestination(final String destination) {
    097            try {
    098                this.destination = parseStreamName(destination);
    099            } 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    }