001/*
002 * Copyright 2013 Atteo.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.atteo.moonshine.logging;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.net.URL;
021import java.nio.file.Files;
022import java.nio.file.Path;
023import java.nio.file.StandardOpenOption;
024import java.util.Enumeration;
025import java.util.Properties;
026import java.util.logging.Handler;
027import java.util.logging.LogManager;
028
029import org.atteo.moonshine.directories.FileAccessor;
030import org.slf4j.LoggerFactory;
031import org.slf4j.bridge.SLF4JBridgeHandler;
032
033import ch.qos.logback.classic.LoggerContext;
034import ch.qos.logback.classic.joran.JoranConfigurator;
035import ch.qos.logback.classic.jul.LevelChangePropagator;
036import ch.qos.logback.core.joran.spi.JoranException;
037
038/**
039 * Logging implementation using Logback.
040 *
041 * <p>
042 * Logging steps:
043 * 1. At first logging is configured using Logback internal mechanism. It finds logback.xml on classpath
044 * and reads configuration from there. We don't have yet any place for storage, so we log provide logback.xml
045 * which will log WARNings and Moonshine startup messages to the console.
046 * 2. During early bootstrap we disable default JUL logger which prints everything to the console
047 * and we start SLF4J bridge which forwards all JUL logs to SLF4J
048 * 3. Finally we load logback-moonshine.xml file which configures full logging.
049 * </p>
050 */
051public class Logback implements Logging {
052    private final LoggingCommandLineParameters parameters = new LoggingCommandLineParameters();
053
054    /**
055     * Disable JUL logging and redirect all logs though SLF4J.
056     * @see <a href="http://stackoverflow.com/questions/2533227/how-can-i-disable-the-default-console-handler-while-using-the-java-logging-api>How can I disable the default console handler</a>
057     * @throws SecurityException
058     */
059    protected void redirectLogsToSLF4J() {
060        java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger("");
061        for (Handler handler : rootLogger.getHandlers()) {
062            rootLogger.removeHandler(handler);
063        }
064        SLF4JBridgeHandler.install();
065    }
066
067    protected void propagateLogbackLevelsToJul() {
068        // Propagate logging levels to JUL for performance reasons, for details see:
069        // http://logback.qos.ch/manual/configuration.html#LevelChangePropagator
070        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
071        context.addListener(new LevelChangePropagator());
072    }
073
074    protected void loadFinalLoggingConfiguration(FileAccessor fileAccessor, Properties properties) {
075        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
076        // the context was probably already configured by default configuration
077        // rules
078        context.reset();
079
080        try {
081            JoranConfigurator configurator = new JoranConfigurator();
082            configurator.setContext(context);
083            for (String property : properties.stringPropertyNames()) {
084                context.putProperty(property, properties.getProperty(property));
085            }
086            if (parameters.getLogLevel() != null) {
087                context.putProperty("log.level", parameters.getLogLevel().name());
088            }
089            Enumeration<URL> resources = this.getClass().getClassLoader().getResources("logback-moonshine.xml");
090            for (; resources.hasMoreElements();) {
091                URL resource = resources.nextElement();
092                try (InputStream inputStream = resource.openStream()) {
093                    configurator.doConfigure(inputStream);
094                }
095            }
096            for (Path path : fileAccessor.getConfigFiles("logback-moonshine.xml")) {
097                try (InputStream inputStream = Files.newInputStream(path, StandardOpenOption.READ)) {
098                    configurator.doConfigure(inputStream);
099                }
100            }
101        } catch (JoranException|IOException e) {
102            throw new RuntimeException(e);
103        }
104    }
105
106    @Override
107    public void earlyBootstrap() {
108        redirectLogsToSLF4J();
109        propagateLogbackLevelsToJul();
110    }
111
112    @Override
113    public Object getParameters() {
114        return parameters;
115    }
116
117    @Override
118    public void initialize(FileAccessor fileAccessor, Properties properties) {
119        loadFinalLoggingConfiguration(fileAccessor, properties);
120    }
121}