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: StandardLogger.java,v 1.2 2006-06-15 13:47:00 sinisa Exp $
022: */
023: package com.lutris.logging;
024:
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.PrintWriter;
030: import java.util.Hashtable;
031:
032: import com.lutris.util.Config;
033: import com.lutris.util.ConfigException;
034: import com.lutris.util.ConfigFile;
035: import com.lutris.util.KeywordValueException;
036:
037: /**
038: * Standard implementation of the <CODE>Logger</CODE>. This is
039: * general-purpose logging facility. A client that needs additional
040: * functionality can either extend this class or provide there own
041: * implementationm of <CODE>Logger</CODE>. <P>
042: *
043: * Currently this is a bare-bones class that writes INFO and above
044: * levels to stderr and all others to a log file.
045: *
046: * @author Mark Diekhans
047: * @see com.lutris.logging.Logger
048: * @see com.lutris.logging.LogChannel
049: * @see com.lutris.logging.StandardLogChannel
050: */
051: public class StandardLogger extends Logger {
052:
053: private static final String LOG_SECTION = "Server";
054: private static final String LOG_FILE = "LogFile";
055: private static final String LOG_TO_FILE = "LogToFile";
056: private static final String LOG_TO_STDERR = "LogToStderr";
057:
058: /**
059: * Table of level names to level numbers. While level configuration
060: * is local to a facility, a global table is kept assigning numbers
061: * to each level name.
062: */
063: private Hashtable levelNumbers = new Hashtable();
064:
065: /**
066: * Table translating level number to name and the largest entry in the
067: * array that is valid. Will be expanded if needed.
068: */
069: protected String[] levelNames = new String[MAX_STD_LEVEL * 2];
070: protected int numLevels = 0;
071:
072: /**
073: * Table of levels that are to be enabled.
074: * Accessed directly by the channel. If null, ignored.
075: */
076: protected boolean[] enabledLevelFlags = null;
077:
078: /**
079: * Table of levels that are to be written to the log file.
080: * Accessed directly by the channel. If null, ignored.
081: */
082: protected boolean[] logFileLevelFlags = null;
083:
084: /**
085: * Table of levels that are to be written to stderr
086: * Accessed directly by the channel. If null, then
087: * the default behavior of writing serious standard
088: * levels to stderr is in affect.
089: */
090: protected boolean[] stderrLevelFlags = null;
091:
092: /**
093: * Log file name.
094: */
095: File activeLogFile;
096:
097: /**
098: * Log file writter. Use directly by channels.
099: */
100: PrintWriter logFileStream;
101:
102: /**
103: * Stderr writter. Use directly by channels.
104: */
105: PrintWriter stderrStream;
106:
107: /**
108: * Table of <CODE>StandardLogChannel<CODE> objects, indexed by facility
109: * name.
110: */
111: private Hashtable logChannels = new Hashtable();
112:
113: /**
114: * Construct a new logger. Configuration is not done now, to allow
115: * the logger to be created very early.
116: *
117: * @param makeCentral Make this object the central logging object.
118: */
119: public StandardLogger(boolean makeCentral) {
120: int level;
121:
122: for (level = 0; level <= MAX_STD_LEVEL; level++) {
123: String name = standardLevelNames[level];
124:
125: levelNumbers.put(name, new Integer(level));
126: levelNames[level] = name;
127: }
128: numLevels = level;
129: if (makeCentral) {
130: centralLogger = this ;
131: }
132: }
133:
134: /**
135: * Get maximum level number in a set of level names.
136: *
137: * @param levels String names of levels.
138: * @return The maximum level number
139: */
140: private int getMaxLevel(String[] levels) {
141: int levelNum;
142: int maxLevelNum = 0;
143:
144: for (int idx = 0; idx < levels.length; idx++) {
145: levelNum = getLevel(levels[idx]);
146: if (levelNum > maxLevelNum) {
147: maxLevelNum = levelNum;
148: }
149: }
150: return maxLevelNum;
151: }
152:
153: /**
154: * Generate a boolean array for all of the listed levels, indicating
155: * if they are enabled.
156: *
157: * @param levels String names of levels.
158: * @param maxLevelNum Size to make the array.
159: */
160: private boolean[] getLevelStateArray(String[] levels,
161: int maxLevelNum) {
162: int levelNum;
163: // Initialize the stated.
164: boolean[] levelNums = new boolean[maxLevelNum + 1];
165:
166: for (int idx = 0; idx < levels.length; idx++) {
167: levelNums[getLevel(levels[idx])] = true;
168: }
169: return levelNums;
170: }
171:
172: /**
173: * Switch a log file; replacing the old one with a new one.
174: *
175: *
176: * @param logFile The new log file.
177: * @return The File object for the previous log file, or null if there
178: * wasn't one.
179: * @exception java.io.IOException If an error occurs opening the log file.
180: */
181: public synchronized File switchLogFile(File logFile)
182: throws java.io.IOException {
183: PrintWriter oldLogFileStream = logFileStream;
184: File oldActiveLogFile = activeLogFile;
185: // Append output stream without auto-flush (we do it explictly).
186: FileOutputStream out = new FileOutputStream(logFile.getPath(),
187: true);
188:
189: logFileStream = new PrintWriter(out, false);
190: activeLogFile = logFile;
191: // Close old, if it exists. Waiting for any accessors.
192: if (oldLogFileStream != null) {
193: synchronized (oldLogFileStream) {
194: oldLogFileStream.close();
195: }
196: }
197: return oldActiveLogFile;
198: }
199:
200: /**
201: * Configure the logger. All current configuration is discarded.
202: * This is a simplistic initial implementation that just allows
203: * directing to a single log file or stderr on a level basis.
204: * A more complete interface will be provided in the future.
205: *
206: * @param logFile The log file to write to.
207: * @param fileLevels List of levels that will be written to the file.
208: * @param stderrLevels List of levels that will be written to stderr.
209: * The same level may appear in both lists.
210: * @exception java.io.IOException If an error occurs opening the log file.
211: */
212: public synchronized void configure(File logFile,
213: String[] fileLevels, String[] stderrLevels)
214: throws java.io.IOException {
215: // Ensure that the directory exists.
216: if (logFile.getParent() != null) {
217: new File(logFile.getParent()).mkdirs();
218: }
219: // Output streams without auto-flush (we do it explictly).
220: switchLogFile(logFile);
221: stderrStream = new PrintWriter(System.err, false);
222:
223: /*
224: * Tables must be created after streams, as they are
225: * the checked before accessing the stream. Care is taken
226: * that all three arrays that are indexed by level are of
227: * the same size and never shrink on reconfigure. This
228: * mean no synchronization is required to access them. Also
229: * enabled table must be done last.
230: */
231: int maxLevelNum;
232: int levelNum;
233:
234: if (enabledLevelFlags != null) {
235: maxLevelNum = enabledLevelFlags.length - 1;
236: } else {
237: maxLevelNum = MAX_STD_LEVEL;
238: }
239: levelNum = getMaxLevel(fileLevels);
240: if (levelNum > maxLevelNum) {
241: maxLevelNum = levelNum;
242: }
243: levelNum = getMaxLevel(stderrLevels);
244: if (levelNum > maxLevelNum) {
245: maxLevelNum = levelNum;
246: }
247: // Build boolean tables.
248: logFileLevelFlags = getLevelStateArray(fileLevels, maxLevelNum);
249: stderrLevelFlags = getLevelStateArray(stderrLevels, maxLevelNum);
250: enabledLevelFlags = new boolean[maxLevelNum + 1];
251: for (int idx = 0; idx < logFileLevelFlags.length; idx++) {
252: if (logFileLevelFlags[idx]) {
253: enabledLevelFlags[idx] = true;
254: }
255: }
256: for (int idx = 0; idx < stderrLevelFlags.length; idx++) {
257: if (stderrLevelFlags[idx]) {
258: enabledLevelFlags[idx] = true;
259: }
260: }
261: }
262:
263: /**
264: * Create a log channel.
265: */
266: private synchronized StandardLogChannel createChannel(
267: String facility) {
268: StandardLogChannel channel = (StandardLogChannel) logChannels
269: .get(facility);
270:
271: if (channel == null) {
272: channel = new StandardLogChannel(facility, this );
273: logChannels.put(facility, channel);
274: }
275: return channel;
276: }
277:
278: /**
279: * Get the log channel object for a facility. For a given facility,
280: * the same object is always returned.
281: *
282: * @param facility Facility the channel is associated with.
283: */
284: public LogChannel getChannel(String facility) {
285: StandardLogChannel channel = (StandardLogChannel) logChannels
286: .get(facility);
287:
288: if (channel == null) {
289: // Slow path, synchronized
290: channel = createChannel(facility);
291: }
292: return channel;
293: }
294:
295: /**
296: * Create a log level.
297: */
298: private synchronized Integer createLevel(String level) {
299: Integer intLevel = (Integer) levelNumbers.get(level);
300:
301: if (intLevel == null) {
302: intLevel = new Integer(numLevels);
303: levelNames[numLevels] = level;
304: levelNumbers.put(level, intLevel);
305: numLevels++;
306: }
307: return intLevel;
308: }
309:
310: /**
311: * Convert a symbolic level to an integer identifier,
312: * creating it if it doesn't exist
313: *
314: * @param level Symbolic level that is to be checked.
315: * @return The numeric level identifier
316: */
317: public synchronized int getLevel(String level) {
318: Integer intLevel = (Integer) levelNumbers.get(level);
319:
320: if (intLevel == null) {
321: // Slow path, synchronized
322: intLevel = createLevel(level);
323: }
324: return intLevel.intValue();
325: }
326:
327: /**
328: * Convert an int to a symbolic level name.
329: *
330: * @param level an int level.
331: * @return The String symolic level name or null if there is not one.
332: */
333: public String getLevelName(int level) {
334: if ((level >= 0) && (level < numLevels)) {
335: return levelNames[level];
336: } else {
337: return null;
338: }
339: }
340:
341: /**
342: * Configure Logger with given config file, interpreting of config file is
343: * logger implementation specific.
344: *
345: * @param confFilePath Path to configuration file.
346: */
347: public void configure(String confFilePath) throws ConfigException {
348: try {
349: FileInputStream configFIS = new FileInputStream(
350: confFilePath);
351: ConfigFile cFile = new ConfigFile(configFIS);
352: Config config = cFile.getConfig();
353:
354: configFIS.close();
355: Config logConfig = (Config) config.getSection(LOG_SECTION);
356:
357: configure(logConfig);
358: } catch (KeywordValueException kve) {
359: throw new ConfigException(
360: "Error parsing configuration for logger.", kve);
361: } catch (IOException ioe) {
362: throw new ConfigException("Error configuring logger.", ioe);
363: }
364: }
365:
366: /**
367: * Configure Logger with given config section
368: *
369: * @param logConfig containing parameters for configuring logger
370: */
371: public void configure(Config logConfig) throws ConfigException {
372: if (logConfig == null) {
373: throw new ConfigException(
374: "Cannot configure logger. Config is null.");
375: } else {
376: String logFile = null;
377:
378: logFile = logConfig.getString(LOG_FILE);
379: String[] toFile = null;
380:
381: toFile = logConfig.getStrings(LOG_TO_FILE);
382: String[] toStderr = null;
383:
384: toStderr = logConfig.getStrings(LOG_TO_STDERR);
385: File theLogFile = new File(logFile);
386:
387: try {
388: configure(theLogFile, toFile, toStderr);
389: } catch (IOException ioe) {
390: throw new ConfigException("Error configuring logger.",
391: ioe);
392: }
393: }
394: }
395: }
|