001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: MonologLogger.java,v 1.3 2007-10-19 10:05:39 sinisa Exp $
022: */
023: package com.lutris.logging;
024:
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.IOException;
028: import java.util.Enumeration;
029: import java.util.Hashtable;
030: import java.util.Iterator;
031: import java.util.Locale;
032: import java.util.Properties;
033: import java.util.ResourceBundle;
034: import java.util.Set;
035:
036: import javax.management.MBeanServerConnection;
037: import javax.management.ObjectName;
038: import javax.management.remote.JMXConnector;
039: import javax.management.remote.JMXConnectorFactory;
040: import javax.management.remote.JMXServiceURL;
041:
042: import org.objectweb.util.monolog.Monolog;
043: import org.objectweb.util.monolog.api.LoggerFactory;
044: import org.objectweb.util.monolog.wrapper.common.Configurable;
045:
046: import com.lutris.util.Config;
047: import com.lutris.util.ConfigException;
048:
049: /**
050: * Monolog implementation of the <CODE>Logger</CODE>. This is
051: * general-purpose logging facility. A client that needs additional
052: * functionality can either extend this class or provide there own
053: * implementationm of <CODE>Logger</CODE>. <P>
054: *
055: * Currently this is a bare-bones class that writes INFO and above
056: * levels to stderr and all others to a log file.
057: *
058: * @author Sinisa Milosevic
059: * @see com.lutris.logging.Logger
060: * @see com.lutris.logging.LogChannel
061: */
062: public class MonologLogger extends com.lutris.logging.Logger {
063:
064: /**
065: * object name of the MBean which represents the Tomcat's session manager
066: */
067: static ObjectName objectName;
068:
069: /**
070: * Table of level names to level numbers. While level configuration
071: * is local to a facility, a global table is kept assigning numbers
072: * to each level name.
073: */
074: private Hashtable levelNumbers = new Hashtable();
075:
076: /**
077: * Table translating level number to name and the largest entry in the
078: * array that is valid. Will be expanded if needed.
079: */
080: protected String[] levelNames = new String[MAX_STD_LEVEL * 2];
081: protected int numLevels = 0;
082:
083: /**
084: * Table of levels that are to be enabled.
085: * Accessed directly by the channel. If null, ignored.
086: */
087: protected boolean[] enabledLevelFlags = null;
088:
089: /**
090: * Table of levels that are to be written to the log file.
091: * Accessed directly by the channel. If null, ignored.
092: */
093: protected boolean[] logFileLevelFlags = null;
094:
095: /**
096: * Table of levels that are to be written to stderr
097: * Accessed directly by the channel. If null, then
098: * the default behavior of writing serious standard
099: * levels to stderr is in affect.
100: */
101: protected boolean[] stderrLevelFlags = null;
102:
103: /**
104: * Default Monolog configuration file
105: */
106: protected String DEFAULT_LOG_CONFIG_FILE = "trace.properties";
107:
108: /**
109: * Log file name.
110: */
111: File activeLogFile;
112:
113: /**
114: * .
115: */
116: LoggerFactory lf;
117:
118: private static ResourceBundle rb = null;
119: public static String PROPERTY_FILE;
120:
121: /**
122: * Table of <CODE>StandardLogChannel<CODE> objects, indexed by facility
123: * name.
124: */
125: private Hashtable logChannels = new Hashtable();
126:
127: String defaultSeparatorLine = "";
128: Hashtable appSeparatorLine = new Hashtable();
129:
130: /**
131: * Construct a new logger. Configuration is not done now, to allow
132: * the logger to be created very early.
133: *
134: * @param makeCentral Make this object the central logging object.
135: */
136: public MonologLogger(boolean makeCentral) {
137: int level;
138:
139: for (level = 0; level <= MAX_STD_LEVEL; level++) {
140: String name = standardLevelNames[level];
141:
142: levelNumbers.put(name, new Integer(level));
143: levelNames[level] = name;
144: }
145: numLevels = level;
146: if (makeCentral) {
147: centralLogger = this ;
148: }
149:
150: }
151:
152: /**
153: * Get maximum level number in a set of level names.
154: *
155: * @param levels String names of levels.
156: * @return The maximum level number
157: */
158: private int getMaxLevel(String[] levels) {
159: int levelNum;
160: int maxLevelNum = 0;
161:
162: for (int idx = 0; idx < levels.length; idx++) {
163: levelNum = getLevel(levels[idx]);
164: if (levelNum > maxLevelNum) {
165: maxLevelNum = levelNum;
166: }
167: }
168: return maxLevelNum;
169: }
170:
171: /**
172: * Configure the logger. All current configuration is discarded.
173: * This is a simplistic initial implementation that just allows
174: * directing to a single log file or stderr on a level basis.
175: * A more complete interface will be provided in the future.
176: *
177: * @param logFile The log file to write to.
178: * @param fileLevels List of levels that will be written to the file.
179: * @param stderrLevels List of levels that will be written to stderr.
180: * The same level may appear in both lists.
181: * @exception java.io.IOException If an error occurs opening the log file.
182: */
183:
184: public synchronized void configure(String monologConfFile)
185: throws ConfigException {
186:
187: if (monologConfFile == null || monologConfFile.length() == 0) {
188: throw new ConfigException(
189: "impossible to configure monolog without configuration file");
190: }
191: // Load the configuration in a Properties object
192: String b = null;
193: Properties p = new Properties();
194: FileInputStream fis = null;
195: try {
196: fis = new FileInputStream(monologConfFile);
197: p.load(fis);
198: // Search the class name of the LoggerFactory which must be instanciated
199: b = p.getProperty("log.config.classname", null);
200:
201: if (b == null) {
202: throw new ConfigException(
203: "Malformed configuration log file:"
204: + " log.config.classname not available");
205: }
206:
207: // Instanciate the LoggerFactory
208: lf = (LoggerFactory) Class.forName(b).newInstance();
209: // Configure with the properties
210: p.put(Configurable.LOG_CONFIGURATION_TYPE,
211: Configurable.PROPERTY);
212: p.put(Configurable.LOG_CONFIGURATION_FILE, monologConfFile);
213: // p.put(Configurable.LOG_CONFIGURATION_FILE_USE_CLASSPATH, "true");
214:
215: ((Configurable) lf).configure(p);
216:
217: fis.close();
218: // PropertiesConfAccess.load(p, lf, (HandlerFactory) lf, (LevelFactory) lf);
219: } catch (Exception e) {
220: try {
221: fis.close();
222: } catch (IOException e1) {
223: e1.printStackTrace();
224: }
225: throw new ConfigException(
226: "Malformed configuration log file:"
227: + " log.config.classname not available");
228: }
229: }
230:
231: public synchronized void configure() throws ConfigException {
232: String b = null;
233: String propkey;
234: String propvalue;
235: Properties props = new Properties();
236:
237: // Search the classpath for the logger configuration file
238:
239: try {
240: findObjectName();
241: if (objectName != null)
242: PROPERTY_FILE = objectName.getKeyProperty("fname");
243: rb = ResourceBundle.getBundle(PROPERTY_FILE, new Locale(
244: "en", "US"), ClassLoader.getSystemClassLoader());
245:
246: Enumeration enumeration = rb.getKeys();
247:
248: while (enumeration.hasMoreElements()) {
249:
250: propkey = (String) enumeration.nextElement();
251: propvalue = rb.getString(propkey);
252: props.setProperty(propkey, propvalue);
253: }
254:
255: } catch (Exception e) {
256: throw new ConfigException(
257: "Logger configuration file could not be found: logger could not be initialized!");
258: }
259:
260: try {
261:
262: // Search the class name of the LoggerFactory which must be instanciated
263: b = props.getProperty("log.config.classname", null);
264: if (b == null) {
265: throw new ConfigException(
266: "Malformed configuration log file:"
267: + " log.config.classname not available");
268: }
269:
270: // Instanciate the LoggerFactory
271:
272: props.put(Configurable.LOG_CONFIGURATION_TYPE,
273: Configurable.PROPERTY);
274: lf = Monolog.getMonologFactory(b);
275:
276: // PropertiesConfAccess.load(p, lf, (HandlerFactory) lf, (LevelFactory) lf);
277: } catch (Exception e) {
278: throw new ConfigException(
279: "Malformed configuration log file:"
280: + " log.config.classname not available");
281: }
282:
283: }
284:
285: /**
286: * Create a log channel.
287: */
288: private synchronized MonologLogChannel createChannel(String facility) {
289:
290: String sepLine = (String) appSeparatorLine.get(facility);
291: if (sepLine == null)
292: sepLine = defaultSeparatorLine;
293:
294: MonologLogChannel channel = (MonologLogChannel) logChannels
295: .get(facility);
296:
297: if (channel == null) {
298: channel = new MonologLogChannel(facility, lf
299: .getLogger(facility), sepLine);
300: logChannels.put(facility, channel);
301: }
302: return channel;
303: }
304:
305: /**
306: * Get the log channel object for a facility. For a given facility,
307: * the same object is always returned.
308: *
309: * @param facility Facility the channel is associated with.
310: */
311: public LogChannel getChannel(String facility) {
312: MonologLogChannel channel = (MonologLogChannel) logChannels
313: .get(facility);
314:
315: if (channel == null) {
316: // Slow path, synchronized
317: channel = createChannel(facility);
318: }
319: return channel;
320: }
321:
322: /**
323: * Create a log level.
324: */
325: private synchronized Integer createLevel(String level) {
326: Integer intLevel = (Integer) levelNumbers.get(level);
327:
328: if (intLevel == null) {
329: intLevel = new Integer(numLevels);
330: levelNames[numLevels] = level;
331: levelNumbers.put(level, intLevel);
332: numLevels++;
333: }
334: return intLevel;
335: }
336:
337: /**
338: * Convert a symbolic level to an integer identifier,
339: * creating it if it doesn't exist
340: *
341: * @param level Symbolic level that is to be checked.
342: * @return The numeric level identifier
343: */
344: public synchronized int getLevel(String level) {
345: Integer intLevel = (Integer) levelNumbers.get(level);
346:
347: if (intLevel == null) {
348: // Slow path, synchronized
349: intLevel = createLevel(level);
350: }
351: return intLevel.intValue();
352: }
353:
354: /**
355: * Convert an int to a symbolic level name.
356: *
357: * @param level an int level.
358: * @return The String symolic level name or null if there is not one.
359: */
360: public String getLevelName(int level) {
361: if ((level >= 0) && (level < numLevels)) {
362: return levelNames[level];
363: } else {
364: return null;
365: }
366: }
367:
368: /**
369: * Configure Logger with given config section
370: *
371: * @param logConfig containing parameters for configuring logger
372: */
373: public void configure(Config logConfig) throws ConfigException {
374: String fileName = null;
375: File logConfigFile = null;
376: if (logConfig.containsKey("Monolog")) {
377: fileName = logConfig.getString("Monolog");
378: logConfigFile = new File(fileName);
379: if (!logConfigFile.exists()) {
380: fileName = logConfig.getConfigFile().getFile()
381: .getParent()
382: + File.separator + fileName;
383: logConfigFile = new File(fileName);
384: if (!logConfigFile.exists()) {
385: fileName = logConfig.getConfigFile().getFile()
386: .getParent();
387: if (null != fileName) {
388: fileName += File.separator
389: + DEFAULT_LOG_CONFIG_FILE;
390: }
391: }
392: }
393: try {
394: configure(fileName);
395: } catch (Exception ex) {
396: try {
397: configure();
398: } catch (ConfigException cex) {
399: throw new ConfigException(
400: "Cannot configure logger. Config file is null.");
401: }
402: }
403:
404: } else {
405: try {
406: configure();
407: } catch (ConfigException ex) {
408: throw new ConfigException(
409: "Cannot configure logger. Logger not initialized.");
410: }
411: }
412: if (logConfig.containsKey("SeparatorLine")) {
413: String app = logConfig.getString("Server.AppClass");
414: String separator = logConfig.getString("SeparatorLine");
415: int index = app.lastIndexOf(".");
416: String temp = app.substring(index);
417: appSeparatorLine.put(temp.substring(1), separator);
418: }
419:
420: }
421:
422: /**
423: * finds the ObjectName which correspondes to the MBean of the jonas logger
424: */
425: private void findObjectName() throws Exception {
426: JMXConnector connector = null;
427: try {
428:
429: String serviceName = "jonas";
430:
431: // The name of the started jonas server (service name)
432: serviceName = System.getProperty("jonas.name");
433: if (serviceName == null) {
434: serviceName = "jonas";
435: }
436:
437: // The address of the connector server
438: JMXServiceURL url = new JMXServiceURL(
439: "service:jmx:rmi://localhost/jndi/jrmpconnector_"
440: + serviceName);
441:
442: // Connect a JSR 160 JMXConnector to the server side
443: connector = JMXConnectorFactory.connect(url);
444:
445: // Retrieve an MBeanServerConnection that represent the MBeanServer the remote
446: // connector server is bound to
447: MBeanServerConnection connection = connector
448: .getMBeanServerConnection();
449:
450: objectName = new ObjectName(serviceName
451: + ":type=service,name=log,*");
452:
453: Set mBeans = connection.queryNames(objectName, null);
454: int l = mBeans.size();
455: if ((l) > 1) {
456: throw new Exception("MBean set size not equal 1: " + l);
457: } else if (l == 0) {
458: objectName = null;
459: if (connector != null) {
460: try {
461: connector.close();
462: } catch (Exception exept) {
463: }
464: }
465: return;
466: }
467: Iterator i = mBeans.iterator();
468: objectName = (ObjectName) i.next();
469: } catch (Exception e) {
470: throw new Exception(e);
471: } finally {
472: if (connector != null) {
473: try {
474: connector.close();
475: } catch (Exception exept) {
476: }
477: }
478: }
479: return;
480: }
481:
482: }
|