/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.util; import totalcross.io.*; import totalcross.sys.*; /** * A Logger object is used to log messages for a specific system or application * component. Loggers are normally named, using a hierarchical dot-separated * namespace. Logger names can be arbitrary strings, but they should normally be * based on the package name or class name of the logged component, such as * totalcross.net or totalcross.io. In addition it is possible to retrieve one * global "anonymous" Logger that can be used in the whole system. * * A logger can be created or retrieved (if it already exists) by calling getLogger. * * To dispose a logger after using it, just call dispose and it will be permanently * discarded. * * To log a message, you may call the log method or any of the other convenience * methods (info, severe, entering, etc). * * @author Bruno Soares * @version 1.0 04 Jun 2008 */ public class Logger { private String name; private Vector outputHandlers; private int level; private byte[] separator; private StringBuffer sbuf = new StringBuffer(64); private Time time = new Time(); /** OFF is a special level that can be used to turn off logging. */ public static final int OFF = 0; /** FINEST indicates a highly detailed tracing message. */ public static final int FINEST = 1; /** FINER indicates a fairly detailed tracing message. */ public static final int FINER = 2; /** FINE is a message level providing tracing information. */ public static final int FINE = 4; /** CONFIG is a message level for static configuration messages. */ public static final int CONFIG = 8; /** INFO is a message level for informational messages. */ public static final int INFO = 16; /** WARNING is a message level indicating a potential problem. */ public static final int WARNING = 32; /** SEVERE is a message level indicating a serious failure. */ public static final int SEVERE = 64; /** ALL indicates that all messages should be logged. */ public static final int ALL = 127; static class DebugConsoleWrapper extends Stream { byte[][] bs = new byte[3][]; int p; StringBuffer sb = new StringBuffer(1024); public int readBytes(byte[] buf, int start, int count) throws IOException { return -1; } public int writeBytes(byte[] buf, int start, int count) throws IOException { bs[p++] = buf; return count; } public void flush() { sb.setLength(0); for (int i = 0; i < p; i++) { sb.append(new String(bs[i])); bs[i] = null; } int l = sb.length(); if (sb.charAt(l-1) == '\n') // cut the last \n sb.setLength(l-1); Vm.debug(sb.toString()); p = 0; } public void close() throws IOException { } } /** Stream where the bytes are written to the debug console */ public static final Stream DEBUG_CONSOLE = new DebugConsoleWrapper(); private static int defaultLevel = ALL; private static String defaultSeparator = "\n"; private static Logger global; private static Hashtable loggers = new Hashtable(5); static { // Initialize TotalCross default loggers getLogger("totalcross", Logger.WARNING | Logger.SEVERE, Logger.DEBUG_CONSOLE); getLogger("totalcross.event", Logger.WARNING | Logger.SEVERE, Logger.DEBUG_CONSOLE); getLogger("totalcross.net", Logger.WARNING | Logger.SEVERE, Logger.DEBUG_CONSOLE); getLogger("totalcross.sys", Logger.WARNING | Logger.SEVERE, Logger.DEBUG_CONSOLE); } /** * Internal use only. */ private Logger(String name) { this.name = name; outputHandlers = new Vector(); level = defaultLevel; separator = defaultSeparator.getBytes(); } /** * Returns the logger with a specific name, keeping its level and output * handlers unchanged. If the logger does not exist, it will be created * and stored for future use. * @param name The logger name. * @return The logger. * @throws NullPointerException if name is null. */ public static Logger getLogger(String name) { return getLogger(name, -1, null); } /** * Returns the logger with a specific name, setting the level specified * and keeping the output handlers unchanged. If the logger does not exist, * it will be created and stored for future use. * @param name The logger name. * @param level The logger level to set or -1 to keep it unchanged. * @return The logger. * @throws NullPointerException if name is null. */ public static Logger getLogger(String name, int level) { return getLogger(name, level, null); } /** * Returns the logger with a specific name, setting the level specified * and optionally adding the given stream to the list of output handlers. * If the logger does not exist, it will be created and stored for future use. * @param name The logger name. * @param level The logger level to set or -1 to keep it unchanged. * @param outputStream The stream to add to the logger's list of output * handlers or <code>null</code> to keep it unchanged. * @return The logger. * @throws NullPointerException if name is null. */ public static Logger getLogger(String name, int level, Stream outputStream) { // Get logger (create if needed) Logger logger = (Logger)loggers.get(name); if (logger == null) { logger = new Logger(name); loggers.put(name, logger); } // Set new logger level (level = -1 means DO NOT CHANGE the current level) if (level != -1) logger.setLevel(level); // Add DEBUG_CONSOLE to the list of output handlers (if not added yet) if (outputStream != null) logger.addOutputHandler(outputStream); return logger; } /** * Returns the global anonymous logger. * @return Always return the global logger. */ public static Logger getGlobalLogger() { if (global == null) global = new Logger(null); return global; } /** * Returns the logger name. * @return The name provided when this logger was created or null, if this * is the global anonymous logger. */ public String getName() { return name; } /** * Returns the current logger level. * @return An integer number representing the current logger level. To check * if the logger is set to log a specific type of message (SEVERE, WARNING, etc), * just check if the level OR'ed with the message level is different than zero. */ public int getLevel() { return level; } /** * Sets the current logger level. * @param level The logger level to set. This should be a composition of one or more * of the message types constants (FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE) or * OFF, if you want to disable all logging, or ALL, if you want to enable all logging. */ public void setLevel(int level) { this.level = level; } /** * Sets the initial logger level, which will be used when a new logger is created. * @param level The logger level to set. This should be a composition of one or more * of the message types constants (FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE) or * OFF, if you want to disable all logging, or ALL, if you want to enable all logging. */ public static void setDefaultLevel(int level) { defaultLevel = level; } /** * Returns the initial logger level, which is used when a new logger is created. * @return An integer number representing the default logger level. */ public static int getDefaultLevel() { return defaultLevel; } /** * Gets the string used to separate two log messages. * @return The string written after each log message. */ public String getSeparator() { return separator == null ? null : new String(separator); } /** * Sets the string used to separate two log messages. * @param separator The string written after each log message. */ public void setSeparator(String separator) { this.separator = separator == null ? null : separator.getBytes(); } /** * Sets the initial message separator, which will be used when a new logger is * created. A separator is a string that separates two log messages (e.g.: \n * (newline), white spaces, etc). * @param separator The separator string. */ public static void setDefaultSeparator(String separator) { defaultSeparator = separator; } /** * Returns the initial message separator, which is used when a new logger is * created. A separator is a string that separates two log messages (e.g.: \n * (newline), white spaces, etc). * @return The separator string. */ public static String getDefaultSeparator() { return defaultSeparator; } /** * Adds an output stream to the logger's output handler set. This means that every logged * message (depending on the current logger level) will be written to this stream. * @param output The output stream. */ public void addOutputHandler(Stream output) { if (output == null) throw new NullPointerException(); if (outputHandlers.indexOf(output) == -1) // only add handler if it's not in the list yet outputHandlers.addElement(output); } /** * Removes an output handler from the logger's output handler set. * @param output The output stream. * @return True, if and only if, the stream was successfully removed. */ public boolean removeOutputHandler(Stream output) { if (output == null) throw new NullPointerException(); return outputHandlers.removeElement(output); } /** * Get the output handlers associated with this logger. * @return An array of all registered output handlers. */ public Stream[] getOutputHandlers() { Stream[] array = new Stream[outputHandlers.size()]; outputHandlers.copyInto(array); return array; } /** * Logs a given message. * @param level The message level. * @param message The message to log. * @param prependInfo A flag indicating whether this log message must be prepended * with the current date and time, level string, logger name, etc. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void log(int level, String message, boolean prependInfo) { if ((this.level & level) != 0) { StringBuffer sb = sbuf; sb.setLength(0); if (prependInfo) { time.update(); Convert.appendTimeStamp(sb, time, true, true); // guich@tc123_62 if (name != null) sb.append(" - ").append(name); sb.append(" - "); switch (level) { case FINEST: sb.append("[ FINEST ] "); break; case FINER: sb.append("[ FINER ] "); break; case FINE: sb.append("[ FINE ] "); break; case CONFIG: sb.append("[ CONFIG ] "); break; case INFO: sb.append("[ INFO ] "); break; case WARNING: sb.append("[ WARNING ] "); break; case SEVERE: sb.append("[ SEVERE ] "); break; } } if (message == null) message = "null"; byte[] b1 = Convert.getBytes(sb); // guich@tc123_35: don't append the message into the StringBuffer... byte[] b2 = message.getBytes(); // ... just use its bytes directly for (int i = outputHandlers.size() - 1; i >= 0; i--) { try { Stream s = (Stream)outputHandlers.items[i]; if (b1 != null) s.writeBytes(b1, 0, b1.length); s.writeBytes(b2, 0, b2.length); if (separator != null) s.writeBytes(separator, 0, separator.length); if (s instanceof DebugConsoleWrapper) ((DebugConsoleWrapper)s).flush(); else if (level == SEVERE && s instanceof File) // bruno@tc120: make sure to flush the file when a severe logging happens (exceptions, for example) ((File)s).flush(); } catch (IOException e) {} } } } private static final byte[] NULL_BYTES = "null".getBytes(); /** * Used internally. */ public void logInfo(StringBuffer message) // guich@tc123_62 { if ((this.level & INFO) != 0) { byte[] b2 = message == null? NULL_BYTES : Convert.getBytes(message); // ... just use its bytes directly for (int i = outputHandlers.size() - 1; i >= 0; i--) { try { Stream s = (Stream)outputHandlers.items[i]; s.writeBytes(b2, 0, b2.length); if (separator != null) s.writeBytes(separator, 0, separator.length); if (s instanceof DebugConsoleWrapper) ((DebugConsoleWrapper)s).flush(); } catch (IOException e) {} } } } /** * Log a FINEST message. If the logger is currently enabled for the FINEST * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void finest(String message) { log(FINEST, message, true); } /** * Log a FINER message. If the logger is currently enabled for the FINER * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void finer(String message) { log(FINER, message, true); } /** * Log a FINE message. If the logger is currently enabled for the FINE * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void fine(String message) { log(FINE, message, true); } /** * Log a CONFIG message. If the logger is currently enabled for the CONFIG * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void config(String message) { log(CONFIG, message, true); } /** * Log a INFO message. If the logger is currently enabled for the INFO * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void info(String message) { log(INFO, message, true); } /** * Log a WARNING message. If the logger is currently enabled for the WARNING * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void warning(String message) { log(WARNING, message, true); } /** * Log a SEVERE message. If the logger is currently enabled for the SEVERE * message level then the given message is written to all the registered output * handler streams. * @param message The message to log. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void severe(String message) { log(SEVERE, message, true); } /** * Log a method entry. This is a convenience method that can be used to log * entry to a method. A record with message "ENTRY", log level FINER, and * the given sourceMethod and sourceClass is logged. * @param sourceClass The name of class that issued the logging request. * @param sourceMethod name of method that is being entered. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void entering(String sourceClass, String sourceMethod, String params) { log(FINER, "ENTRY " + sourceClass + "." + sourceMethod + "(" + params + ")", true); } /** * Log a method return. This is a convenience method that can be used to log * entry to a method. A record with message "RETURN", log level FINER, and * the given sourceMethod and sourceClass is logged. * @param sourceClass The name of class that issued the logging request. * @param sourceMethod The name of method that is being returned. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void exiting(String sourceClass, String sourceMethod) { log(FINER, "RETURN " + sourceClass + "." + sourceMethod, true); } /** * Log throwing an exception. This is a convenience method to log that a method * is terminating by throwing an exception. The logging is done using the * FINER level. If the logger is currently enabled for the given message level * then the record is forwarded to all registered output handlers. * The record's message is set to "THROW". * @param sourceClass he name of class that issued the logging request. * @param sourceMethod The name of method that is throwing the exception. * @param thrown The Throwable that is being thrown. * @throws NullPointerException if one or more registered output handler * streams are null or this logger has been disposed. */ public void throwing(String sourceClass, String sourceMethod, Throwable thrown) { log(SEVERE, "THROW " + sourceClass + "." + sourceMethod + ": " + Vm.getStackTrace(thrown), true); } /** * Permanently discard this logger, removing it from the loggers registry. * @param closeOutputHandlers If <code>true</code>, all output handler streams * will be closed before disposing this logger. */ public void dispose(boolean closeOutputHandlers) { if (name != null) // the global logger cannot be disposed { if (closeOutputHandlers) for (int i = outputHandlers.size() - 1; i >= 0; i--) { try { Stream s = (Stream)outputHandlers.items[i]; if (s instanceof ResizeRecord) // guich@tc124_1: close the underlying stream. ((ResizeRecord) s).getStream().close(); // close the PDB associated to it, since ResizeRecord no longer closes the underlying stream else s.close(); } catch (IOException ex) { } } loggers.remove(name); name = null; outputHandlers = null; } } }