* Add log level info such as [INFO], [ERROR], [WARN] to log output.

* Add Logger.Entry class to avoid unnecessary String concatenation.
* Unify Logger classes code a little bit, making better use of inheritance.
* Run logger thread 3 times per second instead of 4 times.
* Add Javadoc comments and tags.
This commit is contained in:
hns 2006-10-31 14:57:55 +00:00
parent be47e80327
commit dee9082f1c
3 changed files with 90 additions and 109 deletions

View file

@ -41,12 +41,11 @@ public class FileLogger extends Logger implements Log {
DecimalFormat nformat = new DecimalFormat("000"); DecimalFormat nformat = new DecimalFormat("000");
DateFormat aformat = new SimpleDateFormat("yyyy-MM-dd"); DateFormat aformat = new SimpleDateFormat("yyyy-MM-dd");
// timestamp of last log message
long lastMessage;
/** /**
* Create a file logger. The actual file names do have numbers appended and are * Create a file logger. The actual file names do have numbers appended and are
* rotated every x bytes. * rotated every x bytes.
* @param directory the directory
* @param name the log file base name
*/ */
protected FileLogger(String directory, String name) { protected FileLogger(String directory, String name) {
this.name = name; this.name = name;
@ -59,21 +58,16 @@ public class FileLogger extends Logger implements Log {
if (!logdir.exists()) { if (!logdir.exists()) {
logdir.mkdirs(); logdir.mkdirs();
} }
// create a synchronized list for log entries since different threads may
// attempt to modify the list at the same time
entries = Collections.synchronizedList(new LinkedList());
lastMessage = System.currentTimeMillis();
} }
/** /**
* Open the file and get a writer to it. This will either rotate the log files * Open the file and get a writer to it. If the file already exists, this will
* or it will return a writer that appends to an existing file. * return a writer that appends to an existing file if it is from today, or
* otherwise rotate the old log file and start a new one.
*/ */
private synchronized void openFile() { private synchronized void openFile() {
try { try {
if (logfile.exists() && (logfile.lastModified() < lastMidnight())) { if (logfile.exists() && (logfile.lastModified() < Logging.lastMidnight())) {
// rotate if a log file exists and is NOT from today // rotate if a log file exists and is NOT from today
File archive = rotateLogFile(); File archive = rotateLogFile();
// gzip rotated log file in a separate thread // gzip rotated log file in a separate thread
@ -105,48 +99,22 @@ public class FileLogger extends Logger implements Log {
} }
/** /**
* This is called by the runner thread to perform actual output. * This is called by the runner thread to to make sure we have an open writer.
*/ */
synchronized void write() { protected synchronized void ensureOpen() {
if (entries.isEmpty()) { // open a new writer if writer is null or the log file has been deleted
return; if (writer == null || !logfile.exists()) {
} openFile();
try {
int l = entries.size();
if (writer == null || !logfile.exists()) {
// open/create the log file if necessary
openFile();
}
for (int i = 0; i < l; i++) {
String entry = (String) entries.remove(0);
writer.println(entry);
}
writer.flush();
} catch (Exception x) {
int size = entries.size();
if (size > 1000) {
// more than 1000 entries queued plus exception - something
// is definitely wrong with this logger. Write a message to std err and
// discard queued log entries.
System.err.println("Error writing log file " + this + ": " + x);
System.err.println("Discarding " + size + " log entries.");
entries.clear();
}
} }
} }
/** /**
* Rotate log files, closing the file writer and renaming the old * Rotate log files, closing the file writer and renaming the old
* log file. Returns the renamed log file for zipping, or null if * log file. Returns the renamed log file for zipping, or null if
* the log file couldn't be rotated. * the log file couldn't be rotated.
* *
* @return the old renamed log file, or null * @return the old renamed log file, or null
* @throws IOException if an i/o error occurred
*/ */
protected synchronized File rotateLogFile() throws IOException { protected synchronized File rotateLogFile() throws IOException {
// if the logger is not file based do nothing. // if the logger is not file based do nothing.
@ -197,36 +165,13 @@ public class FileLogger extends Logger implements Log {
} }
/** /**
* Return an object which identifies this logger. * Return an object which identifies this logger.
* @return the logger's name
*/ */
public String getName() { public String getName() {
return name; return name;
} }
/**
* Append a message to the log.
*/
public void log(String msg) {
lastMessage = System.currentTimeMillis();
// it's enough to render the date every second
if ((lastMessage - 1000) > dateLastRendered) {
renderDate();
}
entries.add(dateCache + msg);
}
/**
*
*
* @return the timestamp for last midnight in millis
*/
private static long lastMidnight() {
return Logging.nextMidnight() - 86400000;
}
/** /**
* a Thread class that zips up a file, filename will stay the same. * a Thread class that zips up a file, filename will stay the same.
*/ */

View file

@ -27,8 +27,9 @@ import java.util.*;
*/ */
public class Logger implements Log { public class Logger implements Log {
// buffer for log items // buffer for log items; create a synchronized list for log entries since
List entries; // different threads may attempt to modify the list at the same time
List entries = Collections.synchronizedList(new LinkedList());
// Writer for log output // Writer for log output
PrintWriter writer; PrintWriter writer;
@ -50,6 +51,10 @@ public class Logger implements Log {
int logLevel = INFO; int logLevel = INFO;
// timestamp of last log message, used to close file loggers after longer
// periods of inactivity
long lastMessage = System.currentTimeMillis();
/** /**
* zero argument constructor, only here for FileLogger subclass * zero argument constructor, only here for FileLogger subclass
*/ */
@ -59,15 +64,12 @@ public class Logger implements Log {
/** /**
* Create a logger for a PrintStream, such as System.out. * Create a logger for a PrintStream, such as System.out.
* @param out the output stream
*/ */
protected Logger(PrintStream out) { protected Logger(PrintStream out) {
init(); init();
writer = new PrintWriter(out); writer = new PrintWriter(out);
canonicalName = out.toString(); canonicalName = out.toString();
// create a synchronized list for log entries since different threads may
// attempt to modify the list at the same time
entries = Collections.synchronizedList(new LinkedList());
} }
/** /**
@ -115,7 +117,8 @@ public class Logger implements Log {
} }
/** /**
* Return an object which identifies this logger. * Return an object which identifies this logger.
* @return the canonical name of this logger
*/ */
public String getCanonicalName() { public String getCanonicalName() {
return canonicalName; return canonicalName;
@ -123,35 +126,45 @@ public class Logger implements Log {
/** /**
* Append a message to the log. * Append a message to the log.
* @param level a string representing the log level
* @param msg the log message
* @param exception an exception, or null
*/ */
public void log(String msg) { protected void log(String level, Object msg, Throwable exception) {
lastMessage = System.currentTimeMillis();
// it's enough to render the date every second // it's enough to render the date every second
if ((System.currentTimeMillis() - 1000) > dateLastRendered) { if ((lastMessage - 1000) > dateLastRendered) {
renderDate(); renderDate();
} }
// add a safety net so we don't grow indefinitely even if writer thread // add a safety net so we don't grow indefinitely even if writer thread
// has gone. the 2000 entries threshold is somewhat arbitrary. // has gone. the 2000 entries threshold is somewhat arbitrary.
if (entries.size() < 2000) { if (entries.size() < 2000) {
entries.add(dateCache + msg); String message = msg == null ? "null" : msg.toString();
entries.add(new Entry(dateCache, level, message, exception));
} }
} }
/** /**
* This is called by the runner thread to perform actual output. * This is called by the runner thread to perform actual output.
*/ */
void write() { protected synchronized void write() {
if (entries.isEmpty()) { if (entries.isEmpty()) {
return; return;
} }
try { try {
// make sure we have a valid writer
ensureOpen();
int l = entries.size(); int l = entries.size();
for (int i = 0; i < l; i++) { for (int i = 0; i < l; i++) {
String entry = (String) entries.remove(0); Entry entry = (Entry) entries.remove(0);
writer.println(entry); writer.print(entry.date);
writer.print(entry.level);
writer.println(entry.message);
if (entry.exception != null)
entry.exception.printStackTrace(writer);
} }
writer.flush(); writer.flush();
} catch (Exception x) { } catch (Exception x) {
@ -168,8 +181,15 @@ public class Logger implements Log {
} }
} }
/**
* This is called by the runner thread to to make sure we have an open writer.
*/
protected void ensureOpen() {
// nothing to do for console logger
}
// Pre-render the date to use for log messages. Called about once a second. // Pre-render the date to use for log messages. Called about once a second.
static synchronized void renderDate() { protected static synchronized void renderDate() {
Date date = new Date(); Date date = new Date();
dateCache = dformat.format(date); dateCache = dformat.format(date);
dateLastRendered = date.getTime(); dateLastRendered = date.getTime();
@ -203,68 +223,62 @@ public class Logger implements Log {
public void trace(Object parm1) { public void trace(Object parm1) {
if (logLevel <= TRACE) if (logLevel <= TRACE)
log(parm1.toString()); log("[TRACE] ", parm1, null);
} }
public void trace(Object parm1, Throwable parm2) { public void trace(Object parm1, Throwable parm2) {
if (logLevel <= TRACE) if (logLevel <= TRACE)
log(parm1.toString() + "\n" + log("[TRACE] ", parm1, parm2);
getStackTrace(parm2));
} }
public void debug(Object parm1) { public void debug(Object parm1) {
if (logLevel <= DEBUG) if (logLevel <= DEBUG)
log(parm1.toString()); log("[DEBUG] ", parm1, null);
} }
public void debug(Object parm1, Throwable parm2) { public void debug(Object parm1, Throwable parm2) {
if (logLevel <= DEBUG) if (logLevel <= DEBUG)
log(parm1.toString() + "\n" + log("[DEBUG] ", parm1, parm2);
getStackTrace(parm2));
} }
public void info(Object parm1) { public void info(Object parm1) {
if (logLevel <= INFO) if (logLevel <= INFO)
log(parm1.toString()); log("[INFO] ", parm1, null);
} }
public void info(Object parm1, Throwable parm2) { public void info(Object parm1, Throwable parm2) {
if (logLevel <= INFO) if (logLevel <= INFO)
log(parm1.toString() + "\n" + log("[INFO] ", parm1, parm2);
getStackTrace(parm2));
} }
public void warn(Object parm1) { public void warn(Object parm1) {
if (logLevel <= WARN) if (logLevel <= WARN)
log(parm1.toString()); log("[WARN] ", parm1, null);
} }
public void warn(Object parm1, Throwable parm2) { public void warn(Object parm1, Throwable parm2) {
if (logLevel <= WARN) if (logLevel <= WARN)
log(parm1.toString() + "\n" + log("[WARN] ", parm1, parm2);
getStackTrace(parm2));
} }
public void error(Object parm1) { public void error(Object parm1) {
if (logLevel <= ERROR) if (logLevel <= ERROR)
log(parm1.toString()); log("[ERROR] ", parm1, null);
} }
public void error(Object parm1, Throwable parm2) { public void error(Object parm1, Throwable parm2) {
if (logLevel <= ERROR) if (logLevel <= ERROR)
log(parm1.toString() + "\n" + log("[ERROR] ", parm1, parm2);
getStackTrace(parm2));
} }
public void fatal(Object parm1) { public void fatal(Object parm1) {
if (logLevel <= FATAL) if (logLevel <= FATAL)
log(parm1.toString()); log("[FATAL] ", parm1, null);
} }
public void fatal(Object parm1, Throwable parm2) { public void fatal(Object parm1, Throwable parm2) {
if (logLevel <= FATAL) if (logLevel <= FATAL)
log(parm1.toString() + "\n" + log("[FATAL] ", parm1, parm2);
getStackTrace(parm2));
} }
// utility method to get the stack trace from a Throwable as string // utility method to get the stack trace from a Throwable as string
@ -276,4 +290,16 @@ public class Logger implements Log {
return stringWriter.toString(); return stringWriter.toString();
} }
class Entry {
final String date, level, message;
final Throwable exception;
Entry(String date, String level, String message, Throwable exception) {
this.date = date;
this.level = level;
this.message = message;
this.exception = exception;
}
}
} }

View file

@ -73,6 +73,7 @@ public class Logging extends LogFactory {
/** /**
* Get a logger to System.out. * Get a logger to System.out.
* @return a logger that writes to System.out
*/ */
public static Log getConsoleLog() { public static Log getConsoleLog() {
ensureRunning(); ensureRunning();
@ -81,7 +82,9 @@ public class Logging extends LogFactory {
/** /**
* Get a file logger, creating it if it doesn't exist yet. * Get a file logger, creating it if it doesn't exist yet.
* @param logname the base name for the file logger
* @return a file logger
*/ */
public synchronized Logger getFileLog(String logname) { public synchronized Logger getFileLog(String logname) {
Logger log = (Logger) loggerMap.get(logname); Logger log = (Logger) loggerMap.get(logname);
@ -101,9 +104,7 @@ public class Logging extends LogFactory {
} }
public void setAttribute(String name, Object value) { public void setAttribute(String name, Object value) {
if ("logdir".equals(name)) { // FIXME: make log dir changeable at runtime
// FIXME: make log dir changable at runtime
}
} }
public Object getAttribute(String name) { public Object getAttribute(String name) {
@ -216,6 +217,15 @@ public class Logging extends LogFactory {
return cal.getTime().getTime(); return cal.getTime().getTime();
} }
/**
* Returns the timestamp for the last Midnight
*
* @return last midnight timestamp in milliseconds
*/
static long lastMidnight() {
return nextMidnight() - 86400000;
}
/** /**
* The static runner class that loops through all loggers. * The static runner class that loops through all loggers.
*/ */
@ -255,7 +265,7 @@ public class Logging extends LogFactory {
} }
try { try {
wait(250); wait(333);
} catch (InterruptedException ix) { } catch (InterruptedException ix) {
break; break;
} }