001: /**
002: *
003: * Copyright (C) 2000-2004 Enterprise Distributed Technologies Ltd
004: *
005: * www.enterprisedt.com
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Bug fixes, suggestions and comments should be sent to bruce@enterprisedt.com
022: *
023: * Change Log:
024: *
025: * $Log: Logger.java,v $
026: * Revision 1.22 2008-01-09 03:58:54 bruceb
027: * include stack trace
028: *
029: * Revision 1.21 2007-05-29 03:08:53 bruceb
030: * remove comment conflict
031: *
032: * Revision 1.20 2007-05-29 03:08:04 bruceb
033: * add code for enabling logging of thread names
034: *
035: * Revision 1.19 2007-05-15 04:31:59 hans
036: * Made sure each Appender only gets added once in addAppender.
037: *
038: * Revision 1.18 2007/04/26 04:22:53 hans
039: * Added removeAppender
040: *
041: * Revision 1.17 2007/03/27 10:23:08 bruceb
042: * clearAllElements()
043: *
044: * Revision 1.16 2007/03/26 05:23:32 bruceb
045: * add clearAppenders
046: *
047: * Revision 1.15 2007/02/14 00:56:13 hans
048: * Added getLevel
049: *
050: * Revision 1.14 2006/11/14 12:13:24 bruceb
051: * fix bug whereby ALL level was causing exception in log4j
052: *
053: * Revision 1.13 2006/10/27 16:30:04 bruceb
054: * fixed javadoc
055: *
056: * Revision 1.12 2006/10/12 12:38:58 bruceb
057: * synchronized methods
058: *
059: * Revision 1.11 2006/09/08 07:49:35 hans
060: * Fixed error where isEnabledFor didn't work if log4j is being used.
061: * Also added debug methods with MessageFormat-type parameters.
062: *
063: * Revision 1.10 2006/05/22 01:53:03 hans
064: * Added method for dumping byte-arrays as hex
065: *
066: * Revision 1.9 2006/03/16 21:49:07 hans
067: * Added support for logging of exception causes
068: *
069: * Revision 1.8 2005/02/04 12:29:08 bruceb
070: * add exception message to output
071: *
072: * Revision 1.7 2004/10/20 21:03:09 bruceb
073: * catch SecurityExceptions
074: *
075: * Revision 1.6 2004/09/17 12:27:11 bruceb
076: * 1.1 compat
077: *
078: * Revision 1.5 2004/08/31 13:54:50 bruceb
079: * remove compile warnings
080: *
081: * Revision 1.4 2004/08/16 21:08:08 bruceb
082: * made cvsids public
083: *
084: * Revision 1.3 2004/06/25 11:52:26 bruceb
085: * fixed logging bug
086: *
087: * Revision 1.2 2004/05/08 21:13:51 bruceb
088: * renamed property
089: *
090: * Revision 1.1 2004/05/01 16:55:42 bruceb
091: * first cut
092: *
093: *
094: */package com.enterprisedt.util.debug;
095:
096: import java.io.PrintWriter;
097: import java.io.StringWriter;
098: import java.lang.reflect.Method;
099: import java.text.MessageFormat;
100: import java.text.SimpleDateFormat;
101: import java.util.Date;
102: import java.util.Hashtable;
103: import java.util.Vector;
104:
105: import com.enterprisedt.BaseIOException;
106:
107: /**
108: * Logger class that mimics log4j Logger class. If log4j integration
109: * is desired, the "edtftp.log4j" property should be set to "true" and
110: * log4j classes must be in the classpath
111: *
112: * @author Bruce Blackshaw
113: * @version $Revision: 1.22 $
114: */
115: public class Logger {
116:
117: /**
118: * Revision control id
119: */
120: public static String cvsId = "@(#)$Id: Logger.java,v 1.22 2008-01-09 03:58:54 bruceb Exp $";
121:
122: /**
123: * Level of all loggers
124: */
125: private static Level globalLevel;
126:
127: /**
128: * Log thread names of all loggers
129: */
130: private static boolean logThreadNames = false;
131:
132: /**
133: * Timestamp formatter
134: */
135: private SimpleDateFormat format = new SimpleDateFormat(
136: "d MMM yyyy HH:mm:ss.S");
137:
138: /**
139: * Hash of all loggers that exist
140: */
141: private static Hashtable loggers = new Hashtable(10);
142:
143: /**
144: * Vector of all appenders
145: */
146: private static Vector appenders = new Vector(2);
147:
148: /**
149: * Shall we use log4j or not?
150: */
151: private boolean useLog4j = false;
152:
153: /**
154: * Log this logger's thread name
155: */
156: private boolean logThreadName = false;
157:
158: /**
159: * Timestamp
160: */
161: private Date ts = new Date();
162:
163: /**
164: * Class name for this logger
165: */
166: private String clazz;
167:
168: /**
169: * Log4j logging methods
170: */
171: private Method[][] logMethods = null;
172:
173: /**
174: * Log4j toPriority method
175: */
176: private Method toLevelMethod = null;
177:
178: /**
179: * Log4j isEnabledFor method
180: */
181: private Method isEnabledForMethod = null;
182:
183: /**
184: * Logger log4j object
185: */
186: private Object logger = null;
187:
188: /**
189: * Arg arrays for use in invoke
190: */
191: private Object[] argsPlain = new Object[1];
192:
193: /**
194: * Arg arrays for use in invoke
195: */
196: private Object[] argsThrowable = new Object[2];
197:
198: /**
199: * Determine the logging level
200: */
201: static {
202: String level = Level.OFF.toString();
203: try {
204: System
205: .getProperty("edtftp.log.level", Level.OFF
206: .toString());
207: } catch (SecurityException ex) {
208: System.out
209: .println("Could not read property 'edtftp.log.level' due to security permissions");
210: }
211:
212: globalLevel = Level.getLevel(level);
213: }
214:
215: /**
216: * Constructor
217: *
218: * @param clazz class this logger is for
219: * @param uselog4j true if using log4j
220: */
221: private Logger(String clazz, boolean uselog4j) {
222: this .clazz = clazz;
223: this .useLog4j = uselog4j;
224: if (uselog4j)
225: setupLog4j();
226: }
227:
228: /**
229: * Attempt to set up log4j logging. Of course, the classes
230: * must be in the classpath
231: */
232: private synchronized void setupLog4j() {
233: logMethods = new Method[Level.LEVEL_COUNT][2];
234: try {
235: Class log4jLogger = Class
236: .forName("org.apache.log4j.Logger");
237: Class log4jLevel = Class.forName("org.apache.log4j.Level");
238: Class log4jPriority = Class
239: .forName("org.apache.log4j.Priority");
240:
241: // get static logger method & use to get our logger
242: Class[] args = { String.class };
243: Method getLogger = log4jLogger.getMethod("getLogger", args);
244: Object[] invokeArgs = { clazz };
245: logger = getLogger.invoke(null, invokeArgs);
246:
247: // get the logger's methods and store them
248: Class[] plainArgs = { Object.class };
249: Class[] throwableArgs = { Object.class, Throwable.class };
250: logMethods[Level.FATAL_INT][0] = log4jLogger.getMethod(
251: "fatal", plainArgs);
252: logMethods[Level.FATAL_INT][1] = log4jLogger.getMethod(
253: "fatal", throwableArgs);
254: logMethods[Level.ERROR_INT][0] = log4jLogger.getMethod(
255: "error", plainArgs);
256: logMethods[Level.ERROR_INT][1] = log4jLogger.getMethod(
257: "error", throwableArgs);
258: logMethods[Level.WARN_INT][0] = log4jLogger.getMethod(
259: "warn", plainArgs);
260: logMethods[Level.WARN_INT][1] = log4jLogger.getMethod(
261: "warn", throwableArgs);
262: logMethods[Level.INFO_INT][0] = log4jLogger.getMethod(
263: "info", plainArgs);
264: logMethods[Level.INFO_INT][1] = log4jLogger.getMethod(
265: "info", throwableArgs);
266: logMethods[Level.DEBUG_INT][0] = log4jLogger.getMethod(
267: "debug", plainArgs);
268: logMethods[Level.DEBUG_INT][1] = log4jLogger.getMethod(
269: "debug", throwableArgs);
270:
271: // get the toLevel and isEnabledFor methods
272: Class[] toLevelArgs = { int.class };
273: toLevelMethod = log4jLevel
274: .getMethod("toLevel", toLevelArgs);
275: Class[] isEnabledForArgs = { log4jPriority };
276: isEnabledForMethod = log4jLogger.getMethod("isEnabledFor",
277: isEnabledForArgs);
278: } catch (Exception ex) {
279: useLog4j = false;
280: error("Failed to initialize log4j logging", ex);
281: }
282: }
283:
284: /**
285: * Returns the logging level for all loggers.
286: *
287: * @return current logging level.
288: */
289: public static synchronized Level getLevel() {
290: return globalLevel;
291: }
292:
293: /**
294: * Set all loggers to this level
295: *
296: * @param level new level
297: */
298: public static synchronized void setLevel(Level level) {
299: globalLevel = level;
300: }
301:
302: /**
303: * Get a logger for the supplied class
304: *
305: * @param clazz full class name
306: * @return logger for class
307: */
308: public static Logger getLogger(Class clazz) {
309: return getLogger(clazz.getName());
310: }
311:
312: /**
313: * Get a logger for the supplied class
314: *
315: * @param clazz full class name
316: * @return logger for class
317: */
318: public static synchronized Logger getLogger(String clazz) {
319: Logger logger = (Logger) loggers.get(clazz);
320: if (logger == null) {
321: boolean useLog4j = false;
322: try {
323: String log4j = System.getProperty("edtftp.log.log4j");
324: if (log4j != null && log4j.equalsIgnoreCase("true")) {
325: useLog4j = true;
326: }
327: } catch (SecurityException ex) {
328: System.out
329: .println("Could not read property 'edtftp.log.log4j' due to security permissions");
330: }
331: logger = new Logger(clazz, useLog4j);
332: loggers.put(clazz, logger);
333: }
334: return logger;
335: }
336:
337: /**
338: * Add an appender to our list
339: *
340: * @param newAppender
341: */
342: public static synchronized void addAppender(Appender newAppender) {
343: if (!appenders.contains(newAppender))
344: appenders.addElement(newAppender);
345: }
346:
347: /**
348: * Remove an appender to from list
349: *
350: * @param appender
351: */
352: public static synchronized void removeAppender(Appender appender) {
353: appender.close();
354: appenders.removeElement(appender);
355: }
356:
357: /**
358: * Clear all appenders
359: */
360: public static synchronized void clearAppenders() {
361: appenders.removeAllElements();
362: }
363:
364: /**
365: * Close all appenders
366: */
367: public static synchronized void shutdown() {
368: for (int i = 0; i < appenders.size(); i++) {
369: Appender a = (Appender) appenders.elementAt(i);
370: a.close();
371: }
372: }
373:
374: /**
375: * Set global flag for logging thread names as part of the logger names.
376: *
377: * @param logThreadNames true if logging thread names, false otherwise
378: */
379: public static synchronized void logThreadNames(
380: boolean logThreadNames) {
381: Logger.logThreadNames = logThreadNames;
382: }
383:
384: /**
385: * Set flag for logging thread names as part of the logger names for this instance
386: * of the logger.
387: *
388: * @param logThreadName true if logging thread names, false otherwise
389: */
390: public synchronized void logThreadName(boolean logThreadName) {
391: this .logThreadName = logThreadName;
392: }
393:
394: /**
395: * Log a message
396: *
397: * @param level log level
398: * @param message message to log
399: * @param t throwable object
400: */
401: public synchronized void log(Level level, String message,
402: Throwable t) {
403: if (isEnabledFor(level)) {
404: if (useLog4j)
405: log4jLog(level, message, t);
406: else
407: ourLog(level, message, t);
408: }
409: }
410:
411: /**
412: * Calls log4j's isEnabledFor method.
413: *
414: * @param level logging level to check
415: * @return true if logging is enabled
416: */
417: private boolean log4jIsEnabledFor(Level level) {
418: if (level.equals(Level.ALL)) // log4j doesn't have an 'ALL' level
419: level = Level.DEBUG;
420:
421: try {
422: // convert the level to a Log4j Level object
423: Object[] toLevelArgs = new Object[] { new Integer(level
424: .getLevel()) };
425: Object l = toLevelMethod.invoke(null, toLevelArgs);
426:
427: // call isEnabled
428: Object[] isEnabledArgs = new Object[] { l };
429: Object isEnabled = isEnabledForMethod.invoke(logger,
430: isEnabledArgs);
431:
432: return ((Boolean) isEnabled).booleanValue();
433: } catch (Exception ex) { // there's a few, we don't care what they are
434: ourLog(
435: Level.ERROR,
436: "Failed to invoke log4j toLevel/isEnabledFor method",
437: ex);
438: useLog4j = false;
439: return false;
440: }
441: }
442:
443: /**
444: * Log a message to log4j
445: *
446: * @param level log level
447: * @param message message to log
448: * @param t throwable object
449: */
450: private void log4jLog(Level level, String message, Throwable t) {
451:
452: if (level.equals(Level.ALL)) // log4j doesn't have an 'ALL' level
453: level = Level.DEBUG;
454:
455: // set up arguments
456: Object[] args = null;
457: int pos = -1;
458: if (t == null) {
459: args = argsPlain;
460: pos = 0;
461: } else {
462: args = argsThrowable;
463: args[1] = t;
464: pos = 1;
465: }
466: args[0] = message;
467:
468: // retrieve the correct method
469: Method method = logMethods[level.getLevel()][pos];
470:
471: // and invoke the method
472: try {
473: method.invoke(logger, args);
474: } catch (Exception ex) { // there's a few, we don't care what they are
475: ourLog(Level.ERROR,
476: "Failed to invoke log4j logging method", ex);
477: ourLog(level, message, t);
478: useLog4j = false;
479: }
480: }
481:
482: /**
483: * Log a message to our logging system
484: *
485: * @param level log level
486: * @param message message to log
487: * @param t throwable object
488: */
489: private void ourLog(Level level, String message, Throwable t) {
490: ts.setTime(System.currentTimeMillis());
491: String stamp = format.format(ts);
492: StringBuffer buf = new StringBuffer(level.toString());
493: buf.append(" [");
494: if (logThreadNames || logThreadName)
495: buf.append(Thread.currentThread().getName()).append("_");
496: buf.append(clazz).append("] ").append(stamp).append(" : ")
497: .append(message);
498: if (t != null) {
499: buf.append(" : ").append(t.getMessage());
500: StringWriter sw = new StringWriter();
501: PrintWriter pw = new PrintWriter(sw);
502: pw.println();
503: t.printStackTrace(pw);
504: pw.println();
505: buf.append(sw.toString());
506: }
507: if (appenders.size() == 0) { // by default to stdout
508: System.out.println(buf.toString());
509: while (t != null) {
510: t.printStackTrace(System.out);
511: if (t instanceof BaseIOException) {
512: t = ((BaseIOException) t).getInnerThrowable();
513: if (t != null)
514: System.out.println("CAUSED BY:");
515: } else
516: t = null;
517: }
518: } else {
519: for (int i = 0; i < appenders.size(); i++) {
520: Appender a = (Appender) appenders.elementAt(i);
521: a.log(buf.toString());
522: while (t != null) {
523: a.log(t);
524: if (t instanceof BaseIOException) {
525: t = ((BaseIOException) t).getInnerThrowable();
526: if (t != null)
527: a.log("CAUSED BY:");
528: } else
529: t = null;
530: }
531: }
532: }
533: }
534:
535: /**
536: * Log an info level message
537: *
538: * @param message message to log
539: */
540: public void info(String message) {
541: log(Level.INFO, message, null);
542: }
543:
544: /**
545: * Log an info level message
546: *
547: * @param message message to log
548: * @param t throwable object
549: */
550: public void info(String message, Throwable t) {
551: log(Level.INFO, message, t);
552: }
553:
554: /**
555: * Log a warning level message
556: *
557: * @param message message to log
558: */
559: public void warn(String message) {
560: log(Level.WARN, message, null);
561: }
562:
563: /**
564: * Log a warning level message
565: *
566: * @param message message to log
567: * @param t throwable object
568: */
569: public void warn(String message, Throwable t) {
570: log(Level.WARN, message, t);
571: }
572:
573: /**
574: * Log an error level message
575: *
576: * @param message message to log
577: */
578: public void error(String message) {
579: log(Level.ERROR, message, null);
580: }
581:
582: /**
583: * Log an error level message
584: *
585: * @param message message to log
586: * @param t throwable object
587: */
588: public void error(String message, Throwable t) {
589: log(Level.ERROR, message, t);
590: }
591:
592: /**
593: * Log a fatal level message
594: *
595: * @param message message to log
596: */
597: public void fatal(String message) {
598: log(Level.FATAL, message, null);
599: }
600:
601: /**
602: * Log a fatal level message
603: *
604: * @param message message to log
605: * @param t throwable object
606: */
607: public void fatal(String message, Throwable t) {
608: log(Level.FATAL, message, t);
609: }
610:
611: /**
612: * Log a debug level message
613: *
614: * @param message message to log
615: */
616: public void debug(String message) {
617: log(Level.DEBUG, message, null);
618: }
619:
620: private static String hex[] = { "0", "1", "2", "3", "4", "5", "6",
621: "7", "8", "9", "a", "b", "c", "d", "e", "f" };
622:
623: /**
624: * Log a debug level message
625: *
626: * @param message message to log
627: */
628: public void debug(String message, byte[] data) {
629: log(Level.DEBUG, message, null);
630: int i;
631:
632: StringBuffer hexStr = new StringBuffer();
633: StringBuffer charStr = new StringBuffer();
634: for (i = 0; i < data.length; i++) {
635: byte b = data[i];
636: if ((i > 0) && ((i % 12) == 0)) {
637: log(Level.DEBUG, hexStr.toString() + " "
638: + charStr.toString(), null);
639: hexStr = new StringBuffer();
640: charStr = new StringBuffer();
641: }
642:
643: hexStr.append(hex[(b >> 4) & 0x0f] + hex[b & 0x0f] + " ");
644: charStr.append(b >= ' ' && b <= '~' ? (char) b : '?');
645: }
646:
647: log(Level.DEBUG, hexStr.toString() + " " + charStr.toString(),
648: null);
649: }
650:
651: /**
652: * Logs by substituting in the argument at the location marked in the message
653: * argument by {0}.
654: * Additional MessageFormat formatting instructions may be included. Note that
655: * this method saves processing time by not building the complete string unless
656: * it is necessary; this saves the need for encapsulating
657: * many complete logging statements in an "if (log.isDebugEnabled())" block.
658: * @param message Message containing "substitution marks"
659: * @param arg argument to be substituted at the marked location.
660: */
661: public void debug(String message, Object arg) {
662: if (isDebugEnabled())
663: log(Level.DEBUG, MessageFormat.format(message,
664: new Object[] { arg }), null);
665: }
666:
667: /**
668: * Logs by substituting in the arguments at the locations marked in the message
669: * argument by {#} (where # is a number).
670: * Additional MessageFormat formatting instructions may be included.Note that
671: * this method saves processing time by not building the complete string unless
672: * it is necessary; this saves the need for encapsulating
673: * many complete logging statements in an "if (log.isDebugEnabled())" block.
674: * @param message Message containing "substition marks"
675: * @param arg0 argument to be substituted at the marked location.
676: * @param arg1 argument to be substituted at the marked location.
677: */
678: public void debug(String message, Object arg0, Object arg1) {
679: if (isDebugEnabled())
680: log(Level.DEBUG, MessageFormat.format(message,
681: new Object[] { arg0, arg1 }), null);
682: }
683:
684: /**
685: * Logs by substituting in the arguments at the locations marked in the message
686: * argument by {#} (where # is a number).
687: * Additional MessageFormat formatting instructions may be included.Note that
688: * this method saves processing time by not building the complete string unless
689: * it is necessary; this saves the need for encapsulating
690: * many complete logging statements in an "if (log.isDebugEnabled())" block.
691: * @param message Message containing "substition marks"
692: * @param arg0 argument to be substituted at the marked location.
693: * @param arg1 argument to be substituted at the marked location.
694: * @param arg2 argument to be substituted at the marked location.
695: */
696: public void debug(String message, Object arg0, Object arg1,
697: Object arg2) {
698: if (isDebugEnabled())
699: log(Level.DEBUG, MessageFormat.format(message,
700: new Object[] { arg0, arg1, arg2 }), null);
701: }
702:
703: /**
704: * Logs by substituting in the arguments at the locations marked in the message
705: * argument by {#} (where # is a number).
706: * Additional MessageFormat formatting instructions may be included.Note that
707: * this method saves processing time by not building the complete string unless
708: * it is necessary; this saves the need for encapsulating
709: * many complete logging statements in an "if (log.isDebugEnabled())" block.
710: * @param message Message containing "substition marks"
711: * @param arg0 argument to be substituted at the marked location.
712: * @param arg1 argument to be substituted at the marked location.
713: * @param arg2 argument to be substituted at the marked location.
714: * @param arg3 argument to be substituted at the marked location.
715: */
716: public void debug(String message, Object arg0, Object arg1,
717: Object arg2, Object arg3) {
718: if (isDebugEnabled())
719: log(Level.DEBUG, MessageFormat.format(message,
720: new Object[] { arg0, arg1, arg2, arg3 }), null);
721: }
722:
723: /**
724: * Logs by substituting in the arguments at the locations marked in the message
725: * argument by {#} (where # is a number).
726: * Additional MessageFormat formatting instructions may be included.Note that
727: * this method saves processing time by not building the complete string unless
728: * it is necessary; this saves the need for encapsulating
729: * many complete logging statements in an "if (log.isDebugEnabled())" block.
730: * @param message Message containing "substition marks"
731: * @param arg0 argument to be substituted at the marked location.
732: * @param arg1 argument to be substituted at the marked location.
733: * @param arg2 argument to be substituted at the marked location.
734: * @param arg3 argument to be substituted at the marked location.
735: * @param arg4 argument to be substituted at the marked location.
736: */
737: public void debug(String message, Object arg0, Object arg1,
738: Object arg2, Object arg3, Object arg4) {
739: if (isDebugEnabled())
740: log(Level.DEBUG, MessageFormat.format(message,
741: new Object[] { arg0, arg1, arg2, arg3, arg4 }),
742: null);
743: }
744:
745: /**
746: * Log a debug level message
747: *
748: * @param message message to log
749: * @param t throwable object
750: */
751: public void debug(String message, Throwable t) {
752: log(Level.DEBUG, message, t);
753: }
754:
755: /**
756: * Is logging enabled for the supplied level?
757: *
758: * @param level level to test for
759: * @return true if enabled
760: */
761: public synchronized boolean isEnabledFor(Level level) {
762: if (useLog4j) {
763: return log4jIsEnabledFor(level);
764: } else
765: return globalLevel.isGreaterOrEqual(level);
766: }
767:
768: /**
769: * Is logging enabled for the supplied level?
770: *
771: * @return true if enabled
772: */
773: public boolean isDebugEnabled() {
774: return isEnabledFor(Level.DEBUG);
775: }
776:
777: /**
778: * Is logging enabled for the supplied level?
779: *
780: * @return true if enabled
781: */
782: public boolean isInfoEnabled() {
783: return isEnabledFor(Level.INFO);
784: }
785: }
|