001: /*
002: * Copyright 2007 The Kuali Foundation
003: *
004: * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
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: */
016: package org.kuali.rice.config.logging;
017:
018: import java.io.ByteArrayInputStream;
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.FileNotFoundException;
022: import java.io.IOException;
023: import java.util.Properties;
024:
025: import javax.xml.parsers.DocumentBuilder;
026: import javax.xml.parsers.DocumentBuilderFactory;
027:
028: import org.apache.log4j.LogManager;
029: import org.apache.log4j.Logger;
030: import org.apache.log4j.PropertyConfigurator;
031: import org.apache.log4j.helpers.FileWatchdog;
032: import org.apache.log4j.helpers.LogLog;
033: import org.apache.log4j.spi.LoggerRepository;
034: import org.apache.log4j.xml.DOMConfigurator;
035: import org.kuali.rice.config.Config;
036: import org.kuali.rice.core.Core;
037: import org.kuali.rice.lifecycle.BaseLifecycle;
038: import org.springframework.util.Log4jConfigurer;
039: import org.springframework.util.ResourceUtils;
040: import org.w3c.dom.Document;
041:
042: /**
043: * Lifecycle implementation that initializes and shuts down Log4J logging
044: */
045: public class Log4jLifeCycle extends BaseLifecycle {
046:
047: /**
048: * Convenience constant representing a minute in milliseconds
049: */
050: private static final int MINUTE = 60 * 1000;
051:
052: /**
053: * Location of default/automatic Log4J configuration properties, in Spring ResourceUtils resource/url syntax
054: */
055: private static final String AUTOMATIC_LOGGING_CONFIG_URL = "classpath:META-INF/workflow-log4j.properties";
056:
057: /**
058: * Default settings reload interval to use in the case that the settings are reloadable (i.e. they originate from a file)
059: */
060: private static final int DEFAULT_RELOAD_INTERVAL = 5 * MINUTE; // 5 minutes
061:
062: /**
063: * Non-static and non-final so that it can be reset after configuration is read
064: */
065: private Logger log = Logger.getLogger(getClass());
066:
067: public void start() throws Exception {
068: // obtain the root workflow config
069: Config config = Core.getRootConfig();
070:
071: // first check for in-line xml configuration
072: String log4jconfig = config
073: .getProperty(Config.LOG4J_SETTINGS_XML);
074: if (log4jconfig != null) {
075: try {
076: DocumentBuilder b = DocumentBuilderFactory
077: .newInstance().newDocumentBuilder();
078: Document doc = b.parse(new ByteArrayInputStream(
079: log4jconfig.getBytes()));
080: DOMConfigurator.configure(doc.getDocumentElement());
081: // now get the reconfigured log instance
082: log = Logger.getLogger(getClass());
083: } catch (Exception e) {
084: log.error(
085: "Error parsing Log4J configuration settings: "
086: + log4jconfig, e);
087: }
088: // next check for in-line properties configuration
089: } else if ((log4jconfig = config
090: .getProperty(Config.LOG4J_SETTINGS_PROPS)) != null) {
091: Properties p = new Properties(config.getProperties());
092: try {
093: p
094: .load(new ByteArrayInputStream(log4jconfig
095: .getBytes()));
096: PropertyConfigurator.configure(p);
097: log = Logger.getLogger(getClass());
098: } catch (IOException ioe) {
099: log.error(
100: "Error loading Log4J configuration settings: "
101: + log4jconfig, ioe);
102: }
103: // check for an external file location specification
104: } else if ((log4jconfig = config
105: .getProperty(Config.LOG4J_SETTINGS_PATH)) != null) {
106: log.info("Configuring Log4J logging.");
107:
108: int reloadInterval = DEFAULT_RELOAD_INTERVAL;
109:
110: String log4jReloadInterval = config
111: .getProperty(Config.LOG4J_SETTINGS_RELOADINTERVAL_MINS);
112: if (log4jReloadInterval != null) {
113: try {
114: reloadInterval = new Integer(log4jReloadInterval)
115: .intValue()
116: * MINUTE;
117: } catch (NumberFormatException nfe) {
118: log.warn("Invalid reload interval: "
119: + log4jReloadInterval
120: + ", using default: 5 minutes");
121: }
122: }
123:
124: // if we are using a specific version of Log4j for which we have written subclasses that allow
125: // variable substitution using core config, then use those custom classes to do so
126: // otherwise use the plain Spring Log4jConfigurer
127: if ("1.2.13".equals(getLog4jVersion())) {
128: log
129: .info("Using custom Log4j 1.2.13 configurer to make workflow config properties accessible");
130: // use custom impl based on 1.2.13 to insert workflow config properties for resolution
131:
132: WorkflowLog4j_1_2_13_Configurer
133: .initLoggingWithProperties(config
134: .getProperties(), log4jconfig,
135: reloadInterval);
136: } else {
137: log.info("Using standard Spring Log4jConfigurer");
138: // just use standard Spring configurer
139: Log4jConfigurer
140: .initLogging(log4jconfig, reloadInterval);
141: }
142:
143: log = Logger.getLogger(getClass());
144: // finally fall back to a Log4J configuration shipped with workflow
145: } else {
146: Log4jConfigurer.initLogging(AUTOMATIC_LOGGING_CONFIG_URL,
147: DEFAULT_RELOAD_INTERVAL);
148: log = Logger.getLogger(getClass());
149: }
150: super .start();
151: }
152:
153: /**
154: * Uses reflection to attempt to obtain the ImplementationVersion of the org.apache.log4j
155: * package from the jar manifest.
156: * @return the value returned from Package.getPackage("org.apache.log4j").getImplementationVersion()
157: * or null if package is not found
158: */
159: private static String getLog4jVersion() {
160: Package p = Package.getPackage("org.apache.log4j");
161: if (p == null)
162: return null;
163: return p.getImplementationVersion();
164: }
165:
166: /**
167: * Subclasses the Spring Log4jConfigurer to expose a static method which accepts an initial set of
168: * properties (to use for variable substitution)
169: * @author Kuali Rice Team (kuali-rice@googlegroups.com)
170: */
171: private static final class WorkflowLog4j_1_2_13_Configurer extends
172: Log4jConfigurer {
173: public static void initLoggingWithProperties(Properties props,
174: String location, long refreshInterval)
175: throws FileNotFoundException {
176: File file = ResourceUtils.getFile(location);
177: if (!file.exists()) {
178: throw new FileNotFoundException("Log4J config file ["
179: + location + "] not found");
180: }
181: if (location.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
182: DOMConfigurator.configureAndWatch(file
183: .getAbsolutePath(), refreshInterval);
184: } else {
185: assert (props != null);
186: WorkflowLog4j_1_2_13_PropertyConfigurator
187: .configureAndWatch(props, file
188: .getAbsolutePath(), refreshInterval);
189: }
190: }
191: }
192:
193: /**
194: * Subclasses the Log4j 1.2.13 PropertyConfigurator to add a static method which accepts an initial
195: * set of properties (to use for variable substitution)
196: */
197: protected static final class WorkflowLog4j_1_2_13_PropertyConfigurator
198: extends PropertyConfigurator {
199: static public void configureAndWatch(
200: final Properties initialProperties,
201: String configFilename, long delay) {
202: // cannot just use a subclass and pass the initial properties to constructor as the super constructor
203: // is invoked before the properties member can be set, and doOnChange will be called from constructor
204: // with null properties
205: // so instead create an anonymous subclass with closure that includes initialProperties
206: FileWatchdog watchDog = new FileWatchdog(configFilename) {
207: public void doOnChange() {
208: new WorkflowLog4j_1_2_13_PropertyConfigurator()
209: .doConfigure(initialProperties,
210: this .filename, LogManager
211: .getLoggerRepository());
212: }
213: };
214: watchDog.setDelay(delay);
215: watchDog.start();
216: }
217:
218: public void doConfigure(Properties initialProperties,
219: String configFileName, LoggerRepository hierarchy) {
220: Properties props = new Properties();
221: props.putAll(initialProperties);
222:
223: try {
224: FileInputStream istream = new FileInputStream(
225: configFileName);
226: props.load(istream);
227: istream.close();
228: } catch (IOException e) {
229: LogLog.error("Could not read configuration file ["
230: + configFileName + "].", e);
231: LogLog.error("Ignoring configuration file ["
232: + configFileName + "].");
233: return;
234: }
235: // If we reach here, then the config file is alright.
236: doConfigure(props, hierarchy);
237: }
238: }
239:
240: public void stop() throws Exception {
241: LogManager.shutdown();
242: super.stop();
243: }
244:
245: }
|