Package org.apache.geronimo.system.logging.log4j

Source Code of org.apache.geronimo.system.logging.log4j.Log4jService$CachingLog4jLogFactory

/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
*  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 org.apache.geronimo.system.logging.log4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Set;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.PatternSyntaxException;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogConfigurationException;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.geronimo.gbean.GBeanInfoBuilder;
import org.apache.geronimo.gbean.GBeanLifecycle;
import org.apache.geronimo.kernel.log.GeronimoLogFactory;
import org.apache.geronimo.kernel.log.GeronimoLogging;
import org.apache.geronimo.system.serverinfo.ServerInfo;
import org.apache.geronimo.system.logging.SystemLog;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.FileAppender;

/**
* A Log4j logging service.
*
* @version $Rev: 407944 $ $Date: 2006-05-20 04:53:12 +0200 (Sat, 20 May 2006) $
*/
public class Log4jService implements GBeanLifecycle, SystemLog {
    // A substitution variable in the file path in the config file
    private final static Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{.*?\\}");
    // Next 6 are patterns that identify log messages in our default format
    private final static Pattern DEFAULT_ANY_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (TRACE|DEBUG|INFO|WARN|ERROR|FATAL) .*");
    private final static Pattern DEFAULT_FATAL_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d FATAL .*");
    private final static Pattern DEFAULT_ERROR_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (ERROR|FATAL) .*");
    private final static Pattern DEFAULT_WARN_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (WARN|ERROR|FATAL) .*");
    private final static Pattern DEFAULT_INFO_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (INFO|WARN|ERROR|FATAL) .*");
    private final static Pattern DEFAULT_DEBUG_START = Pattern.compile("^\\d\\d\\:\\d\\d\\:\\d\\d\\,\\d\\d\\d (DEBUG|INFO|WARN|ERROR|FATAL) .*");
    // Next 6 are patterns that identify log messages if the user changed the format -- but we assume the log level is in there somewhere
    private final static Pattern UNKNOWN_ANY_START = Pattern.compile("(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)");
    private final static Pattern UNKNOWN_FATAL_START = Pattern.compile("FATAL");
    private final static Pattern UNKNOWN_ERROR_START = Pattern.compile("(ERROR|FATAL)");
    private final static Pattern UNKNOWN_WARN_START = Pattern.compile("(WARN|ERROR|FATAL)");
    private final static Pattern UNKNOWN_INFO_START = Pattern.compile("(INFO|WARN|ERROR|FATAL)");
    private final static Pattern UNKNOWN_DEBUG_START = Pattern.compile("(DEBUG|INFO|WARN|ERROR|FATAL)");
    // Pattern that matches a single line  (used to calculate line numbers and check for follow-on stack traces)
    private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE);


    /**
     * The URL to the configuration file.
     */
    private String configurationFile;

    /**
     * The time (in seconds) between checking for new config.
     */
    private int refreshPeriod;

    /**
     * The properties service
     */
    private final ServerInfo serverInfo;

    /**
     * The URL watch timer (in daemon mode).
     */
    private Timer timer = new Timer(true);

    /**
     * A monitor to check when the config URL changes.
     */
    private TimerTask monitor;

    /**
     * Last time the file was changed.
     */
    private long lastChanged = -1;

    /**
     * Is this service running?
     */
    private boolean running = false;

    /**
     * Construct a <code>Log4jService</code>.
     *
     * @param configurationFile The log4j configuration file.
     * @param refreshPeriod The refresh refreshPeriod (in seconds).
     */
    public Log4jService(final String configurationFile, final int refreshPeriod, ServerInfo serverInfo) {
        this.refreshPeriod = refreshPeriod;
        this.configurationFile = configurationFile;
        this.serverInfo = serverInfo;
    }

    /**
     * Gets the level of the root logger.
     */
    public synchronized String getRootLoggerLevel() {
        Level level = LogManager.getRootLogger().getLevel();

        if (level != null) {
            return level.toString();
        }

        return null;
    }

    /**
     * Sets the level of the root logger.
     *
     * @param level The level to change the logger to.
     */
    public synchronized void setRootLoggerLevel(final String level) {

        String currentLevel = this.getRootLoggerLevel();

        // ensure that the level has really been changed
        if (!currentLevel.equals(level)) {
            LogManager.getRootLogger().setLevel(XLevel.toLevel(level));
        }
    }

    /**
     * Gets the level of the logger of the give name.
     *
     * @param logger The logger to inspect.
     */
    public String getLoggerEffectiveLevel(final String logger) {
        if (logger == null) {
            throw new IllegalArgumentException("logger is null");
        }

        Level level = LogManager.getLogger(logger).getEffectiveLevel();

        if (level != null) {
            return level.toString();
        }

        return null;
    }

    /**
     * Gets the level of the logger of the give name.
     *
     * @param logger The logger to inspect.
     */
    public String getLoggerLevel(final String logger) {
        if (logger == null) {
            throw new IllegalArgumentException("logger is null");
        }

        Level level = LogManager.getLogger(logger).getLevel();

        if (level != null) {
            return level.toString();
        }

        return null;
    }

    /**
     * Sets the level for a logger of the give name.
     *
     * @param logger The logger to change level
     * @param level The level to change the logger to.
     */
    public void setLoggerLevel(final String logger, final String level) {
        if (logger == null) {
            throw new IllegalArgumentException("logger is null");
        }
        if (level == null) {
            throw new IllegalArgumentException("level is null");
        }

        Logger.getLogger(logger).setLevel(XLevel.toLevel(level));
    }

    /**
     * Get the refresh period.
     *
     * @return the refresh period (in seconds)
     */
    public synchronized int getRefreshPeriodSeconds() {
        return refreshPeriod;
    }

    /**
     * Set the refresh period.
     *
     * @param period the refresh period (in seconds)
     * @throws IllegalArgumentException if refresh period is <= 0
     */
    public synchronized void setRefreshPeriodSeconds(final int period) {
        if (period < 1) {
            throw new IllegalArgumentException("Refresh period must be > 0");
        }

        if (this.refreshPeriod != period) {
            this.refreshPeriod = period;
            schedule();
        }
    }

    /**
     * Get the logging configuration URL.
     *
     * @return the logging configuration URL
     */
    public synchronized String getConfigFileName() {
        return configurationFile;
    }

    /**
     * Set the logging configuration URL.
     *
     * @param configurationFile the logging configuration file
     */
    public synchronized void setConfigFileName(final String configurationFile) {
        if (configurationFile == null) {
            throw new IllegalArgumentException("configurationFile is null");
        }

        // ensure that the file name has really been updated
        if (!this.configurationFile.equals(configurationFile)) {
            this.configurationFile = configurationFile;
            lastChanged = -1;
        }
    }

    /**
     * Get the content of logging configuration file.
     *
     * @return the content of logging configuration file
     */
    public synchronized String getConfiguration() {
        File file = resolveConfigurationFile();
        if (file == null || !file.canRead()) {
            return null;
        }
        Reader in = null;
        try {
            StringBuffer configuration = new StringBuffer();
            in = new InputStreamReader(new FileInputStream(file));
            char[] buffer = new char[4096];
            for (int size = in.read(buffer); size >= 0; size = in.read(buffer)) {
                configuration.append(buffer, 0, size);
            }
            return configuration.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * Overwrites the content of logging configuration file.
     *
     * @param configuration the new content of logging configuration file
     */
    public synchronized void setConfiguration(final String configuration) throws IOException {
        if (configuration == null || configuration.length() == 0) {
            throw new IllegalArgumentException("configuration is null or an empty string");
        }

        File file = resolveConfigurationFile();
        if (file == null) {
            throw new IllegalStateException("Configuration file is null");
        }

        // make parent directory if necessary
        if (!file.getParentFile().exists()) {
            if (!file.getParentFile().mkdirs()) {
                throw new IllegalStateException("Could not create parent directory of log configuration file: " + file.getParent());
            }
        }

        // verify that the file is writable or does not exist
        if (file.exists() && !file.canWrite()) {
            throw new IllegalStateException("Configuration file is not writable: " + file.getAbsolutePath());
        }

        OutputStream out = null;
        try {
            out = new FileOutputStream(file);
            out.write(configuration.getBytes());
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public synchronized String[] getLogFileNames() {
        List list = new ArrayList();
        for (Enumeration e = Logger.getRootLogger().getAllAppenders(); e.hasMoreElements();) {
            Object appender = e.nextElement();
            if (appender instanceof FileAppender) {
                list.add(((FileAppender) appender).getFile());
            }
        }
        return (String[]) list.toArray(new String[list.size()]);
    }

    private static SearchResults searchFile(File file, String targetLevel, Pattern textSearch, Integer start, Integer stop, int max, boolean stacks) {
        List list = new LinkedList();
        boolean capped = false;
        int lineCount = 0;
        try {
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            FileChannel fc = raf.getChannel();
            MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
            CharBuffer cb = Charset.forName("US-ASCII").decode(bb); //todo: does Log4J use a different charset on a foreign PC?
            Matcher target = null;
            Matcher any = null;
            Matcher lines = FULL_LINE_PATTERN.matcher(cb);
            Matcher text = textSearch == null ? null : textSearch.matcher("");
            boolean hit = false;
            max = Math.min(max, MAX_SEARCH_RESULTS);
            while(lines.find()) {
                ++lineCount;
                if(target == null) {
                    if(DEFAULT_ANY_START.matcher(cb.subSequence(lines.start(), lines.end())).find()) {
                        target = getDefaultPatternForLevel(targetLevel).matcher("");
                        any = DEFAULT_ANY_START.matcher("");
                    } else {
                        target = getUnknownPatternForLevel(targetLevel).matcher("");
                        any = UNKNOWN_ANY_START.matcher("");
                    }
                }
                if(start != null && start.intValue() > lineCount) {
                    continue;
                }
                if(stop != null && stop.intValue() < lineCount) {
                    continue;
                }
                CharSequence line = cb.subSequence(lines.start(), lines.end());
                target.reset(line);
                if(target.find()) {
                    if(text != null) {
                        text.reset(line);
                        if(!text.find()) {
                            hit = false;
                            continue;
                        }
                    }
                    list.add(new LogMessage(lineCount,line.toString()));
                    if(list.size() > max) {
                        list.remove(0);
                        capped = true;
                    }
                    hit = true;
                } else if(stacks && hit) {
                    any.reset(line);
                    if(!any.find()) {
                        list.add(new LogMessage(lineCount,line.toString()));
                        if(list.size() > max) {
                            list.remove(0);
                            capped = true;
                        }
                    } else {
                        hit = false;
                    }
                }
            }
            fc.close();
            raf.close();
        } catch (Exception e) {} // TODO: improve exception handling
        return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped);
    }

    private static String substituteSystemProps(String source) {
        StringBuffer buf = new StringBuffer();
        int last = 0;
        Matcher m = VARIABLE_PATTERN.matcher(source);
        while(m.find()) {
            buf.append(source.substring(last, m.start()));
            String prop = source.substring(m.start()+2, m.end()-1);
            buf.append(System.getProperty(prop));
            last = m.end();
        }
        buf.append(source.substring(last));
        return buf.toString();
    }

    private static Pattern getDefaultPatternForLevel(String targetLevel) {
        if(targetLevel.equals("FATAL")) {
            return DEFAULT_FATAL_START;
        } else if(targetLevel.equals("ERROR")) {
            return DEFAULT_ERROR_START;
        } else if(targetLevel.equals("WARN")) {
            return DEFAULT_WARN_START;
        } else if(targetLevel.equals("INFO")) {
            return DEFAULT_INFO_START;
        } else if(targetLevel.equals("DEBUG")) {
            return DEFAULT_DEBUG_START;
        } else {
            return DEFAULT_ANY_START;
        }
    }

    private static Pattern getUnknownPatternForLevel(String targetLevel) {
        if(targetLevel.equals("FATAL")) {
            return UNKNOWN_FATAL_START;
        } else if(targetLevel.equals("ERROR")) {
            return UNKNOWN_ERROR_START;
        } else if(targetLevel.equals("WARN")) {
            return UNKNOWN_WARN_START;
        } else if(targetLevel.equals("INFO")) {
            return UNKNOWN_INFO_START;
        } else if(targetLevel.equals("DEBUG")) {
            return UNKNOWN_DEBUG_START;
        } else {
            return UNKNOWN_ANY_START;
        }
    }

    public SearchResults getMatchingItems(String logFile, Integer firstLine, Integer lastLine, String minLevel, String text, int maxResults, boolean includeStackTraces) {
        // Ensure the file argument is really a log file!
        if(logFile == null) {
            throw new IllegalArgumentException("Must specify a log file");
        }
        String[] files = getLogFileNames();
        boolean found = false;
        for (int i = 0; i < files.length; i++) {
            if(files[i].equals(logFile)) {
                found = true;
                break;
            }
        }
        if(!found) {
            throw new IllegalArgumentException("Not a log file!");
        }
        // Check for valid log level
        if(minLevel == null) {
            minLevel = "TRACE";
        } else if(!minLevel.equals("FATAL") && !minLevel.equals("ERROR") && !minLevel.equals("WARN") &&
                !minLevel.equals("INFO") && !minLevel.equals("DEBUG") && !minLevel.equals("TRACE")) {
            throw new IllegalArgumentException("Not a valid log level");
        }
        // Check that the text pattern is valid
        Pattern textPattern = null;
        try {
            textPattern = text == null || text.equals("") ? null : Pattern.compile(text);
        } catch (PatternSyntaxException e) {
            throw new IllegalArgumentException("Bad regular expression '"+text+"'");
        }
        // Make sure we can find the log file
        File log = new File(substituteSystemProps(logFile));
        if(!log.exists()) {
            throw new IllegalArgumentException("Log file "+log.getAbsolutePath()+" does not exist");
        }
        // Run the search
        return searchFile(log, minLevel, textPattern, firstLine, lastLine, maxResults, includeStackTraces);
    }

    /**
     * Force the logging system to reconfigure.
     */
    public void reconfigure() {
        File file = resolveConfigurationFile();
        if (file == null || !file.exists()) {
            return;
        }
       
        // Record the default console log level
        System.setProperty("org.apache.geronimo.log.ConsoleLogLevel", GeronimoLogging.getConsoleLogLevel().toString());

        try {
            URLConfigurator.configure(file.toURL());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        // refresh the level info for every log
        GeronimoLogFactory logFactory = (GeronimoLogFactory) LogFactory.getFactory();
        Set instances = logFactory.getInstances();
        for (Iterator iterator = instances.iterator(); iterator.hasNext();) {
            Object log = iterator.next();
            if (log instanceof CachingLog4jLog) {
                ((CachingLog4jLog)log).updateLevelInfo();
            }
        }
    }

    private synchronized void schedule() {
        if (timer != null) {
            // kill the old monitor
            if (monitor != null) {
                monitor.cancel();
            }

            // start the new one
            monitor = new URLMonitorTask();
            TimerTask task = monitor;
            timer.schedule(monitor, 1000 * refreshPeriod, 1000 * refreshPeriod);
            task.run();
        }
    }

    public void doStart() {
        LogFactory logFactory = LogFactory.getFactory();
        if (logFactory instanceof GeronimoLogFactory) {
            synchronized (this) {
                timer = new Timer(true);

                // Periodically check the configuration file
                schedule();

                // Make sure the root Logger has loaded
                LogManager.getRootLogger();

                reconfigure();

                File file = resolveConfigurationFile();
                if (file != null) {
                    lastChanged = file.lastModified();
                }
            }

            // Change all of the loggers over to use log4j
            GeronimoLogFactory geronimoLogFactory = (GeronimoLogFactory) logFactory;
            synchronized (geronimoLogFactory) {
                if (!(geronimoLogFactory.getLogFactory() instanceof CachingLog4jLogFactory)) {
                    geronimoLogFactory.setLogFactory(new CachingLog4jLogFactory());
                }
            }
        }

        synchronized (this) {
            running = true;
        }
    }

    public synchronized void doStop() {
        running = false;
        if (monitor != null) {
            monitor.cancel();
            monitor = null;
        }
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    public void doFail() {
        doStop();
    }

    private synchronized File resolveConfigurationFile() {
        try {
            return serverInfo.resolveServer(configurationFile);
        } catch (Exception e) {
            return null;
        }
    }

    private class URLMonitorTask extends TimerTask {
        public void run() {
            try {
                long lastModified;
                synchronized (this) {
                    if (running == false) {
                        return;
                    }

                    File file = resolveConfigurationFile();
                    if (file == null) {
                        return;
                    }

                    lastModified = file.lastModified();
                }

                if (lastChanged < lastModified) {
                    lastChanged = lastModified;
                    reconfigure();
                }
            } catch (Exception e) {
            }
        }
    }

    private static class CachingLog4jLogFactory extends LogFactory {
        public Log getInstance(Class clazz) throws LogConfigurationException {
            return getInstance(clazz.getName());
        }

        public Log getInstance(String name) throws LogConfigurationException {
            return new CachingLog4jLog(name);
        }

        public Object getAttribute(String name) {
            return null;
        }

        public String[] getAttributeNames() {
            return new String[0];
        }

        public void release() {
        }

        public void removeAttribute(String name) {
        }

        public void setAttribute(String name, Object value) {
        }
    }

    public static final GBeanInfo GBEAN_INFO;

    static {
        GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(Log4jService.class, "SystemLog");

        infoFactory.addAttribute("configFileName", String.class, true);
        infoFactory.addAttribute("refreshPeriodSeconds", int.class, true);
        infoFactory.addAttribute("configuration", String.class, false);
        infoFactory.addAttribute("rootLoggerLevel", String.class, false);

        infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");

        infoFactory.addOperation("reconfigure");
        infoFactory.addOperation("setLoggerLevel", new Class[]{String.class, String.class});
        infoFactory.addOperation("getLoggerLevel", new Class[]{String.class});
        infoFactory.addOperation("getLoggerEffectiveLevel", new Class[]{String.class});
        infoFactory.addInterface(SystemLog.class);

        infoFactory.setConstructor(new String[]{"configFileName", "refreshPeriodSeconds", "ServerInfo"});

        GBEAN_INFO = infoFactory.getBeanInfo();
    }

    public static GBeanInfo getGBeanInfo() {
        return GBEAN_INFO;
    }
}
TOP

Related Classes of org.apache.geronimo.system.logging.log4j.Log4jService$CachingLog4jLogFactory

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.