Package com.netflix.blitz4j

Source Code of com.netflix.blitz4j.LoggingConfiguration

/*
* Copyright 2012 Netflix, Inc.
*
*    Licensed 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 com.netflix.blitz4j;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.helpers.Loader;
import org.apache.log4j.spi.LoggerFactory;
import org.slf4j.Logger;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.ExpandedConfigurationListenerAdapter;
import com.netflix.config.PropertyListener;
import com.netflix.logging.messaging.BatcherFactory;
import com.netflix.logging.messaging.MessageBatcher;

/**
* The main configuration class that bootstraps the <em>blitz4j</em>
* implementation.
*
* <p>
* The users can either use {@link #configure()} or
* {@link #configure(Properties)} to kick start the configuration. If the
* <code>log4j.configuration</code> is provided, the properties are additionally
* loaded from the provided {@link URL}.
* </p>
*
* <p>
* The list of appenders to be automatically converted can be provided by the
* property <code>log4j.logger.asyncAppenders</code>. The configuration takes
* these appenders and automatically enables them for asynchronous logging.
* </p>
*
* @author Karthik Ranganathan
*
*/
public class LoggingConfiguration implements PropertyListener {

    private static final String LOG4J_ROOT_LOGGER = "log4j.rootLogger";
    private static final String LOG4J_ROOT_CATEGORY = "log4j.rootCategory";
    private static final String LOG4J_PROPERTIES = "log4j.properties";
    private static final String BLITZ_LOGGER_FACTORY = "com.netflix.blitz4j.NFCategoryFactory";
    private static final String PROP_LOG4J_CONFIGURATION = "log4j.configuration";
    private static final Object guard = new Object();
    private static final String PROP_LOG4J_LOGGER_FACTORY = "log4j.loggerFactory";
    private static final String LOG4J_FACTORY_IMPL = "com.netflix.logging.log4jAdapter.NFCategoryFactory";

    private static final String LOG4J_LOGGER_FACTORY = "log4j.loggerFactory";
    private static final String PROP_LOG4J_ORIGINAL_APPENDER_NAME = "originalAppenderName";
    private static final String LOG4J_PREFIX = "log4j.logger";
    private static final String LOG4J_APPENDER_DELIMITER = ".";
    private static final String LOG4J_APPENDER_PREFIX = "log4j.appender";
    private static final String ASYNC_APPENDERNAME_SUFFIX = "_ASYNC";
    private static final String ROOT_CATEGORY = "rootCategory";
    private static final String ROOT_LOGGER = "rootLogger";
   
    private Map<String, String> originalAsyncAppenderNameMap = new HashMap<String, String>();
    private BlitzConfig blitz4jConfig;
    private Properties props = new Properties();
    Properties updatedProps = new Properties();
    private final ExecutorService executorPool;
    private Logger logger;
    private static final int SLEEP_TIME_MS = 200;
    private static final CharSequence PROP_LOG4J_ASYNC_APPENDERS = "log4j.logger.asyncAppenders";

    private static LoggingConfiguration instance = new LoggingConfiguration();

    private LoggingConfiguration() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
        .setDaemon(false).setNameFormat("DynamicLog4jListener").build();

        this.executorPool = new ThreadPoolExecutor(0, 1, 15 * 60,
                TimeUnit.SECONDS, new ArrayBlockingQueue(100), threadFactory);
    }

    /**
     * Kick start the blitz4j implementation
     */
    public void configure() {
        this.configure(null);
    }

    /**
     * Kick start the blitz4j implementation.
     *
     * @param props
     *            - The overriding <em>log4j</em> properties if any.
     */
    public void configure(Properties props) {
        this.originalAsyncAppenderNameMap.clear();
       // First try to load the log4j configuration file from the classpath
        String log4jConfigurationFile = System
        .getProperty(PROP_LOG4J_CONFIGURATION);
       
        NFHierarchy nfHierarchy = null;
        // Make log4j use blitz4j implementations
        if ((!NFHierarchy.class.equals(LogManager.getLoggerRepository()
                .getClass()))) {
            nfHierarchy = new NFHierarchy(new NFRootLogger(
                    org.apache.log4j.Level.INFO));
            org.apache.log4j.LogManager.setRepositorySelector(
                    new NFRepositorySelector(nfHierarchy), guard);
        }
        String log4jLoggerFactory = System
        .getProperty(PROP_LOG4J_LOGGER_FACTORY);
        if (log4jLoggerFactory != null) {
            this.props.setProperty(PROP_LOG4J_LOGGER_FACTORY,
                    log4jLoggerFactory);
            if (nfHierarchy != null) {
                try {
                    LoggerFactory loggerFactory = (LoggerFactory) Class
                    .forName(log4jLoggerFactory).newInstance();
                    nfHierarchy.setLoggerFactory(loggerFactory);
                } catch (Throwable e) {
                    System.err
                    .println("Cannot set the logger factory. Hence reverting to default.");
                    e.printStackTrace();
                }
            }
        } else {
            this.props.setProperty(PROP_LOG4J_LOGGER_FACTORY,
                    BLITZ_LOGGER_FACTORY);

        }
        if (log4jConfigurationFile != null) {
            loadLog4jConfigurationFile(log4jConfigurationFile);
            // First configure without async so that we can capture the output
            // of dependent libraries
            clearAsyncAppenderList();
            PropertyConfigurator.configure(this.props);
        }

        this.blitz4jConfig = new DefaultBlitz4jConfig(props);

        if ((log4jConfigurationFile == null)
                && (blitz4jConfig.shouldLoadLog4jPropertiesFromClassPath())) {

            InputStream in = null;
            try {
                URL url = Loader.getResource(LOG4J_PROPERTIES);
                if (url != null) {
                    in = url.openStream();
                    this.props.load(in);
                }
            } catch (Throwable t) {

            } finally {

                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException ignore) {

                    }
                }
            }

        }
        if (props != null) {
            Enumeration enumeration = props.propertyNames();
            while (enumeration.hasMoreElements()) {
                String key = (String) enumeration.nextElement();
                String propertyValue = props.getProperty(key);
                this.props.setProperty(key, propertyValue);
            }
        }
        this.blitz4jConfig = new DefaultBlitz4jConfig(this.props);
       
   
        String[] asyncAppenderArray = blitz4jConfig.getAsyncAppenders();
        if (asyncAppenderArray == null) {
            return;
        }
        for (int i = 0; i < asyncAppenderArray.length; i++) {
            String oneAppenderName = asyncAppenderArray[i];
            if ((i == 0) || (oneAppenderName == null)) {
                continue;
            }
            oneAppenderName = oneAppenderName.trim();
            String oneAsyncAppenderName = oneAppenderName
            + ASYNC_APPENDERNAME_SUFFIX;
            originalAsyncAppenderNameMap.put(oneAppenderName,
                    oneAsyncAppenderName);
        }
        try {
            convertConfiguredAppendersToAsync(this.props);
        } catch (Throwable e) {
            throw new RuntimeException("Could not configure async appenders ",
                    e);
        }
        // Yes second time init required as properties would have been during async appender conversion
        this.blitz4jConfig = new DefaultBlitz4jConfig(this.props);
        clearAsyncAppenderList();
        PropertyConfigurator.configure(this.props);
        closeNonexistingAsyncAppenders();
        this.logger = org.slf4j.LoggerFactory
        .getLogger(LoggingConfiguration.class);
        ConfigurationManager.getConfigInstance().addConfigurationListener(
                new ExpandedConfigurationListenerAdapter(this));
    }

    private void clearAsyncAppenderList() {
        org.apache.log4j.Logger asyncLogger = LoggerCache.getInstance()
        .getOrCreateLogger("asyncAppenders");
        if (asyncLogger != null) {
            asyncLogger.removeAllAppenders();
        }
    }

    private void loadLog4jConfigurationFile(String log4jConfigurationFile) {
        InputStream in = null;
        try {
            URL url = new URL(log4jConfigurationFile);
            in = url.openStream();
            this.props.load(in);
        } catch (Throwable t) {
            throw new RuntimeException(
                    "Cannot load log4 configuration file specified in "
                    + PROP_LOG4J_CONFIGURATION, t);
        } finally {

            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignore) {

                }
            }
        }
    }

 

    public static LoggingConfiguration getInstance() {
        return instance;
    }

    public BlitzConfig getConfiguration() {
        return this.blitz4jConfig;
    }

    /**
     * Shuts down blitz4j cleanly by flushing out all the async related
     * messages.
     */
    public void stop() {
        MessageBatcher batcher = null;
        for (String originalAppenderName : originalAsyncAppenderNameMap
                .keySet()) {
            String batcherName = AsyncAppender.class.getName() + "."
            + originalAppenderName;
            batcher = BatcherFactory.getBatcher(batcherName);
            if (batcher == null) {
                continue;
            }
            batcher.stop();
        }
        for (String originalAppenderName : originalAsyncAppenderNameMap
                .keySet()) {
            String batcherName = AsyncAppender.class.getName() + "."
            + originalAppenderName;
            batcher = BatcherFactory.getBatcher(batcherName);
            if (batcher == null) {
                continue;
            }
            BatcherFactory.removeBatcher(batcherName);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.netflix.config.PropertyListener#addProperty(java.lang.Object,
     * java.lang.String, java.lang.Object, boolean)
     */
    public void addProperty(Object source, String name, Object value,
            boolean beforeUpdate) {
        if (shouldProcessProperty(name, beforeUpdate)) {
            updatedProps.put(name, value);
            reConfigureAsynchronously();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.netflix.config.PropertyListener#clear(java.lang.Object, boolean)
     */
    public void clear(Object source, boolean beforeUpdate) {
    }

    /*
     * (non-Javadoc)
     *
     * @see com.netflix.config.PropertyListener#clearProperty(java.lang.Object,
     * java.lang.String, java.lang.Object, boolean)
     */
    public void clearProperty(Object source, String name, Object value,
            boolean beforeUpdate) {
        if (shouldProcessProperty(name, beforeUpdate)) {
            updatedProps.remove(name);
            reConfigureAsynchronously();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.netflix.config.PropertyListener#configSourceLoaded(java.lang.Object)
     */
    public void configSourceLoaded(Object source) {
    }

    /*
     * (non-Javadoc)
     *
     * @see com.netflix.config.PropertyListener#setProperty(java.lang.Object,
     * java.lang.String, java.lang.Object, boolean)
     */
    public void setProperty(Object source, String name, Object value,
            boolean beforeUpdate) {
        if (shouldProcessProperty(name, beforeUpdate)) {
            updatedProps.put(name, value);
            reConfigureAsynchronously();
        }
    }

    /**
     * Reconfigure log4j at run-time.
     *
     * @param name
     *            - The name of the property that changed
     * @param value
     *            - The new value of the property
     * @throws FileNotFoundException
     * @throws ConfigurationException
     */
    private void reConfigure() throws ConfigurationException,
    FileNotFoundException {

        Properties consolidatedProps = new Properties();
        consolidatedProps.putAll(props);
        logger.info("Updated properties is :" + updatedProps);
        consolidatedProps.putAll(updatedProps);
        logger.info("The root category for log4j.rootCategory now is "
                + consolidatedProps.getProperty(LOG4J_ROOT_CATEGORY));
        logger.info("The root category for log4j.rootLogger now is "
                + consolidatedProps.getProperty(LOG4J_ROOT_LOGGER));

        // Pause the async appenders so that the appenders are not accessed
        for (String originalAppenderName : originalAsyncAppenderNameMap
                .keySet()) {
            MessageBatcher asyncBatcher = BatcherFactory
            .getBatcher(AsyncAppender.class.getName() + "."
                    + originalAppenderName);
            if (asyncBatcher == null) {
                continue;
            }
            asyncBatcher.pause();
        }

        // Configure log4j using the new set of properties
        configureLog4j(consolidatedProps);
        // Resume all the batchers to continue logging
        for (String originalAppenderName : originalAsyncAppenderNameMap
                .keySet()) {
            MessageBatcher asyncBatcher = BatcherFactory
            .getBatcher(AsyncAppender.class.getName() + "."
                    + originalAppenderName);
            if (asyncBatcher == null) {
                continue;
            }
            asyncBatcher.resume();
        }
    }

    /**
     * Configure log4j with the given properties.
     *
     * @param props
     *            The properties that needs to be configured for log4j
     * @throws ConfigurationException
     * @throws FileNotFoundException
     */
    private void configureLog4j(Properties props)
    throws ConfigurationException, FileNotFoundException {
        if (blitz4jConfig.shouldUseLockFree()
                && (props.getProperty(LOG4J_LOGGER_FACTORY) == null)) {
            props.setProperty(LOG4J_LOGGER_FACTORY, LOG4J_FACTORY_IMPL);
        }
        convertConfiguredAppendersToAsync(props);
        clearAsyncAppenderList();
        logger.info("Configuring log4j with properties :" + props);
        PropertyConfigurator.configure(props);
    }

    /**
     * Asynchronously reconfigure <code>log4j</code>. This make sure there is
     * only one configuration currently in progress and rejects multiple
     * reconfigurations at the same time there by causing logging contentions.
     */
    private void reConfigureAsynchronously() {
        try {
            executorPool.submit(new Runnable() {

                public void run() {
                    try {
                        Thread.sleep(SLEEP_TIME_MS);
                        logger.info("Configuring log4j dynamically");
                        reConfigure();
                    } catch (Throwable th) {
                        logger.error("Cannot dynamically configure log4j :", th);
                    }
                }
            });
        } catch (RejectedExecutionException re) {
            throw re;
        }
    }

    /**
     * Check if the property that is being changed is something that this
     * configuration cares about.
     *
     * The implementation only cares about changes related to <code>log4j</code>
     * properties.
     *
     * @param name
     *            -The name of the property which should be checked.
     * @param beforeUpdate
     *            -true, if this call is made before the property has been
     *            updated, false otherwise.
     * @return
     */
    private boolean shouldProcessProperty(String name, boolean beforeUpdate) {
        if (name == null) {
            logger.warn("The listener got a null value for name");
            return false;
        }
        if (beforeUpdate) {
            return false;
        }
        return name.startsWith(LOG4J_PREFIX);
    }

    /**
     * Convert appenders specified by the property
     * <code>log4j.logger.asyncAppender</code> to the blitz4j Asynchronous
     * appenders.
     *
     * @param props
     *            - The properties that need to be passed into the log4j for
     *            configuration.
     * @throws ConfigurationException
     * @throws FileNotFoundException
     */
    private void convertConfiguredAppendersToAsync(Properties props)
            throws ConfigurationException, FileNotFoundException {
        for (Map.Entry<String, String> originalAsyncAppenderMapEntry : originalAsyncAppenderNameMap
                .entrySet()) {
            String asyncAppenderName = originalAsyncAppenderMapEntry.getValue();
            props.setProperty(LOG4J_APPENDER_PREFIX + LOG4J_APPENDER_DELIMITER
                    + asyncAppenderName, AsyncAppender.class.getName());
            // Set the original appender so that it can be fetched later after
            // configuration
            String originalAppenderName = originalAsyncAppenderMapEntry
                    .getKey();
            props.setProperty(LOG4J_APPENDER_PREFIX + LOG4J_APPENDER_DELIMITER
                    + asyncAppenderName + LOG4J_APPENDER_DELIMITER
                    + PROP_LOG4J_ORIGINAL_APPENDER_NAME, originalAppenderName);
            // Set the batcher to reject the collector request instead of it
            // participating in processing
            this.props.setProperty(
                    "batcher." + AsyncAppender.class.getName() + "."
                            + originalAppenderName + "." + "rejectWhenFull",
                    "true");
            // Set the default value of the processing max threads to 1, if a
            // value is not specified
            String maxThreads = this.props.getProperty (
                    "batcher." + AsyncAppender.class.getName() + "."
                            + originalAppenderName + "." + "maxThreads");
            if (maxThreads == null) {
                this.props.setProperty(
                        "batcher." + AsyncAppender.class.getName() + "."
                                + originalAppenderName + "." + "maxThreads",
                        "1");
            }

            for (Map.Entry mapEntry : props.entrySet()) {
                String key = mapEntry.getKey().toString();
                if ((key.contains(LOG4J_PREFIX) || key.contains(ROOT_CATEGORY) || key
                        .contains(ROOT_LOGGER))
                        && !key.contains(PROP_LOG4J_ASYNC_APPENDERS)
                        && !key.contains(PROP_LOG4J_ORIGINAL_APPENDER_NAME)) {
                    Object value = mapEntry.getValue();
                    if (value != null) {
                        String[] values = (String.class.cast(value)).split(",");
                        String valueString = "";
                        int ctr = 0;
                        for (String oneValue : values) {
                            if (oneValue == null) {
                                continue;
                            }
                            ++ctr;
                            if (originalAppenderName.equals(oneValue.trim())) {
                                oneValue = asyncAppenderName;
                            }
                            if (ctr != values.length) {
                                valueString = valueString + oneValue + ",";
                            } else {
                                valueString = valueString + oneValue;
                            }
                        }
                        mapEntry.setValue(valueString);
                    }
                }
            }
        }
    }

    /**
     * Closes any asynchronous appenders that were not removed during configuration.
     */
    private void closeNonexistingAsyncAppenders() {
        org.apache.log4j.Logger rootLogger = LogManager.getRootLogger();
        if (NFLockFreeLogger.class.isInstance(rootLogger)) {
            ((NFLockFreeLogger)rootLogger).reconcileAppenders();
        }
        Enumeration enums = LogManager.getCurrentLoggers();
        while (enums.hasMoreElements()) {
            Object myLogger = enums.nextElement();
            if (NFLockFreeLogger.class.isInstance(myLogger)) {
                ((NFLockFreeLogger)myLogger).reconcileAppenders();
            }
        }
    }

}
TOP

Related Classes of com.netflix.blitz4j.LoggingConfiguration

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.