View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.context;
17  
18  import java.io.File;
19  import java.net.URL;
20  import java.net.URLClassLoader;
21  import java.text.NumberFormat;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.log4j.PropertyConfigurator;
28  import org.kuali.ole.sys.OLEConstants;
29  import org.kuali.rice.core.api.config.property.ConfigContext;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * This configurer serves 2 purposes:<br>
35   * 1 - Provide the ability to override the bundled <code>log4j.properties<code> with a custom <code>log4j.properties</code> file<br>
36   * 2 - Provide the ability to dynamically alter the log4j configuration at runtime by modifying a <code>log4j.properties</code> on
37   * the file system that is being monitored for changes<br>
38   * <br>
39   * Unless the property <code>ole.fs.log4j.override</code> is set to <code>true</code>, this configurer takes no action and the
40   * default log4j configuration bundled with the application is used.
41   */
42  public class Log4jConfigurer {
43      private static final Logger logger = LoggerFactory.getLogger(Log4jConfigurer.class);
44      private static final double MILLISECONDS_CONVERSION_MULTIPLIER = 60 * 1000;
45      private static final NumberFormat NF = getNumberFormatter();
46  
47      /**
48       * If you set the system property -Dlog4j.debug, you will see that the <code>isOverride()</code> method runs code that issues
49       * logging statements. This means that log4 has already initialized itself and has issued a few logging statements prior to the
50       * point where this method attempts to configure log4j with a custom <code>log4j.properties</code>.<br>
51       * <br>
52       * So this method is re-configuring log4j immediately after log4j uses its internal procedures to automatically configure
53       * itself. <br>
54       * <br>
55       * Kinda funky, but it seems to work ok.<br>
56       * <br>
57       * Logging statements issued after this method finishes honor the new settings.
58       */
59      public static final void configureLogging(boolean doStartupStatsLogging) {
60          boolean override = isOverride();
61          logger.info(OLEConstants.LOG4J_OVERRIDE_KEY + "=" + override);
62          if (!override) {
63              return;
64          }
65  
66          File customConfigFile = getCustomConfigFile();
67          long reloadMillis = getReloadMillis();
68          double minutes = reloadMillis / MILLISECONDS_CONVERSION_MULTIPLIER;
69          logger.info("Reconfiguring log4j using [" + customConfigFile + "] Reload interval is " + NF.format(minutes) + " minutes");
70          PropertyConfigurator.configureAndWatch(customConfigFile.getAbsolutePath(), reloadMillis);
71          debugClasspath();
72      }
73  
74      /**
75       * Return true if we need to override the default log4j configuration with custom settings. False otherwise
76       */
77      protected static boolean isOverride() {
78          String value = ConfigContext.getCurrentContextConfig().getProperty(OLEConstants.LOG4J_OVERRIDE_KEY);
79          return Boolean.parseBoolean(value);
80      }
81  
82      protected static File getCustomConfigFile() {
83          String filename = ConfigContext.getCurrentContextConfig().getProperty(OLEConstants.LOG4J_SETTINGS_FILE_KEY);
84          if (StringUtils.isBlank(filename)) {
85              throw new IllegalStateException("log4j override requested, but the property " + OLEConstants.LOG4J_SETTINGS_FILE_KEY + " is blank");
86          }
87          File file = new File(filename);
88          if (!file.exists()) {
89              throw new IllegalStateException("[" + OLEConstants.LOG4J_SETTINGS_FILE_KEY + "=" + filename + "], but " + filename + " does not exist.");
90          }
91          if (!file.canRead()) {
92              throw new IllegalStateException("[" + OLEConstants.LOG4J_SETTINGS_FILE_KEY + "=" + filename + "], but " + filename + " is not readable.");
93          }
94          return file;
95      }
96  
97      protected static long getReloadMillis() {
98          String s = ConfigContext.getCurrentContextConfig().getProperty(OLEConstants.LOG4J_RELOAD_MINUTES_KEY);
99          try {
100             Double minutes = new Double(s);
101             Double millis = minutes * MILLISECONDS_CONVERSION_MULTIPLIER;
102             return millis.longValue();
103         }
104         catch (NumberFormatException e) {
105             throw new IllegalStateException("Could not parse '" + s + "'", e);
106         }
107     }
108 
109     protected static void debugClasspath() {
110         URLClassLoader ucl = (URLClassLoader) Thread.currentThread().getContextClassLoader();
111         URL[] urls = ucl.getURLs();
112         List<String> files = new ArrayList<String>();
113         for (URL url : urls) {
114             files.add(url.getFile());
115         }
116         Collections.sort(files);
117         logger.debug("Located " + files.size() + " classpath entries");
118         for (String file : files) {
119             logger.debug("Classpath entry: " + file);
120         }
121     }
122 
123     protected static NumberFormat getNumberFormatter() {
124         NumberFormat nf = NumberFormat.getInstance();
125         nf.setGroupingUsed(false);
126         nf.setMaximumFractionDigits(1);
127         nf.setMinimumFractionDigits(1);
128         return nf;
129     }
130 
131 }