1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: Logger.java 2050 2010-12-09 15:31:45Z dmsmith $
21   */
22  package org.crosswire.common.util;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.MissingResourceException;
27  import java.util.logging.Handler;
28  import java.util.logging.Level;
29  import java.util.logging.LogManager;
30  import java.util.logging.LogRecord;
31  
32  /**
33   * This class is very similar to Commons-Logging except it should be even
34   * smaller and have an API closer to the Log4J API (and even J2SE 1.4 logging).
35   * 
36   * This implementation is lazy. The actual internal logger is not initialized
37   * until first use. Turns out that this class indirectly depends upon JSword's
38   * Project class to help find the logging configuration file. If it is not lazy,
39   * it looks in the wrong places for the configuration file.
40   * 
41   * @see gnu.lgpl.License for license details.<br>
42   *      The copyright to this program is held by it's authors.
43   * @author Joe Walker [joe at eireneh dot com]
44   * @author DM Smith [dmsmith555 at yahoo dot com]
45   */
46  public final class Logger {
47      /**
48       * Get a new logger for the class that shows the class, method and line
49       * number of the caller.
50       * @param clazz the class that holds the logger.
51       */
52      public static <T> Logger getLogger(Class<T> clazz) {
53          return getLogger(clazz, true);
54      }
55  
56      /**
57       * Get a new logger for the class that shows the class of the caller.
58       * @param clazz the class that holds the logger.
59       * @param showLocation when true it will get the method and line where logging occurred.
60       */
61      public static <T> Logger getLogger(Class<T> clazz, boolean showLocation) {
62          return new Logger(clazz, showLocation);
63      }
64  
65      /**
66       * Set the level at which output occurs for this Logger.
67       * 
68       * @param newLevel
69       *            the level to apply
70       */
71      public void setLevel(Level newLevel) {
72          logger.setLevel(newLevel);
73      }
74  
75      /**
76       * Stop all logging output
77       */
78      public static synchronized void outputNothing() {
79          level = Level.OFF;
80      }
81  
82      /**
83       * Output a minimum of stuff
84       */
85      public static synchronized void outputInfoMinimum() {
86          level = Level.WARNING;
87      }
88  
89      /**
90       * Output everything
91       */
92      public static synchronized void outputEverything() {
93          level = Level.ALL;
94      }
95  
96      /**
97       * Log a message object with the SEVERE level.
98       * 
99       * @param msg
100      *            the message to log.
101      */
102     public void fatal(String msg) {
103         doLogging(Level.SEVERE, msg, null);
104     }
105 
106     /**
107      * Log a message object with the SEVERE level.
108      * 
109      * @param msg
110      *            the message object to log.
111      */
112     public void fatal(String msg, Throwable th) {
113         doLogging(Level.SEVERE, msg, th);
114     }
115 
116     /**
117      * Log a message object with the WARNING level.
118      * 
119      * @param msg
120      *            the message to log.
121      */
122     public void error(String msg) {
123         doLogging(Level.WARNING, msg, null);
124     }
125 
126     /**
127      * Log a message object with the WARNING level.
128      * 
129      * @param msg
130      *            the message to log.
131      * @param th
132      *            the exception to note when not null
133      */
134     public void error(String msg, Throwable th) {
135         doLogging(Level.WARNING, msg, th);
136     }
137 
138     /**
139      * Log a message object with the INFO level.
140      * 
141      * @param msg
142      *            the message object to log.
143      */
144     public void info(String msg) {
145         doLogging(Level.INFO, msg, null);
146     }
147 
148     /**
149      * Log a message object with the INFO level.
150      * 
151      * @param msg
152      *            the message object to log.
153      * @param th
154      *            the exception to note when not null
155      */
156     public void info(String msg, Throwable th) {
157         doLogging(Level.INFO, msg, th);
158     }
159 
160     /**
161      * Log a message object with the FINE level.
162      * 
163      * @param msg
164      *            the message object to log.
165      */
166     public void warn(String msg) {
167         doLogging(Level.FINE, msg, null);
168     }
169 
170     /**
171      * Log a message object with the FINE level.
172      * 
173      * @param msg
174      *            the message object to log.
175      * @param th
176      *            the exception to note when not null
177      */
178     public void warn(String msg, Throwable th) {
179         doLogging(Level.FINE, msg, th);
180     }
181 
182     /**
183      * Log a message object with the FINEST level.
184      * 
185      * @param msg
186      *            the message object to log.
187      */
188     public void debug(String msg) {
189         doLogging(Level.FINEST, msg, null);
190     }
191 
192     /**
193      * Log a message with the supplied level.
194      * 
195      * @param lev
196      *            the level at which to log.
197      * @param msg
198      *            the message to log.
199      */
200     public void log(Level lev, String msg) {
201         doLogging(lev, msg, null);
202     }
203 
204     /**
205      * Log a message with the supplied level, recording the exception when not
206      * null.
207      * 
208      * @param msg
209      *            the message object to log.
210      */
211     public void log(Level lev, String msg, Throwable th) {
212         doLogging(lev, msg, th);
213     }
214 
215     /**
216      * Create a logger for the class. Wrapped by {@link #java.util.logging.Logger.getLogger(String)}.
217      */
218     private <T> Logger(Class<T> id, boolean showLocation) {
219         this.logger = java.util.logging.Logger.getLogger(id.getName());
220         this.showLocation = showLocation;
221     }
222 
223     // Private method to infer the caller's class and method names
224     private void doLogging(Level theLevel, String message, Throwable th) {
225         initialize();
226 
227         LogRecord logRecord = new LogRecord(theLevel, message);
228         logRecord.setLoggerName(logger.getName());
229         logRecord.setSourceClassName(CallContext.getCallingClass(1).getName());
230         logRecord.setThrown(th);
231 
232         if (showLocation) {
233             String methodName = null;
234             int lineNumber = -1;
235 
236             // Get the stack trace.
237             StackTraceElement[] stack = (new Throwable()).getStackTrace();
238 
239             // First, search back to a method in the Logger class.
240             int ix = 0;
241             while (ix < stack.length) {
242                 StackTraceElement frame = stack[ix];
243                 String cname = frame.getClassName();
244                 if (cname.equals(CLASS_NAME)) {
245                     break;
246                 }
247                 ix++;
248             }
249 
250             // Now search for the first frame with the name of the caller.
251             while (ix < stack.length) {
252                 StackTraceElement frame = stack[ix];
253                 if (!frame.getClassName().equals(CLASS_NAME)) {
254                     // We've found the relevant frame.
255                     methodName = frame.getMethodName();
256                     lineNumber = frame.getLineNumber();
257                     break;
258                 }
259                 ix++;
260             }
261 
262             logRecord.setSourceMethodName(methodName);
263             // This is a non-standard use of sequence number.
264             // We could just subclass LogRecord and add line number.
265             logRecord.setSequenceNumber(lineNumber);
266         }
267 
268         logger.log(logRecord);
269     }
270 
271     private synchronized void initialize() {
272         Logger.establishLogging();
273         Logger.setLevel();
274     }
275 
276     private static void establishLogging() {
277         if (established) {
278             return;
279         }
280         established = true;
281 
282         Exception ex = null;
283         try {
284             InputStream cwConfigStream = ResourceUtil.getResourceAsStream("CWLogging.properties");
285             LogManager.getLogManager().readConfiguration(cwConfigStream);
286         } catch (SecurityException e) {
287             ex = e;
288         } catch (MissingResourceException e) {
289             ex = e;
290         } catch (IOException e) {
291             ex = e;
292         }
293         if (ex != null) {
294             cwLogger.info("Can't load CWLogging.properties", ex);
295         }
296     }
297 
298     private static void setLevel() {
299         // If there was a request to change the minimum level of logging
300         // handle it now.
301         if (Logger.level != null) {
302             // There are two parts of making a log message get out.
303             // It has to be more important than the level:
304             //     a) of the logger
305             //     b) of the handler
306             // So we need to set both.
307             // In the case of the handlers, we set all of them.
308             java.util.logging.Logger rootLogger = java.util.logging.Logger.getLogger(ROOT_LOGGER);
309             Handler[] handlers = rootLogger.getHandlers();
310             for (int index = 0; index < handlers.length; index++) {
311                 handlers[index].setLevel(Level.FINE);
312             }
313             rootLogger.setLevel(Logger.level);
314             // Don't do this again unless asked.
315             Logger.level = null;
316         }
317     }
318 
319     private static final String ROOT_LOGGER = "";
320     private static final String CLASS_NAME = Logger.class.getName();
321     private static volatile boolean established;
322     private static volatile Level level;
323 
324     /**
325      * The actual logger.
326      */
327     private java.util.logging.Logger logger;
328     private static Logger cwLogger = getLogger(Logger.class);
329 
330     /**
331      * Whether we dig into the call stack to get the method and line number of
332      * the caller.
333      */
334     private boolean showLocation;
335 }
336