Pretty much rewrote the Logger class.

- Applied patch From Stefan Pollach that results in better log file rotation
 and gzips old log files
- Added sanity checks that re-creates a log file if it has been moved or
  deleted
- Added sanity check that closes a logger that can't write to its file.
- Added some method for real time introspection of logging system

Todo: Decide what should happen when a Logger can't write to its file.
This commit is contained in:
hns 2002-04-25 16:39:37 +00:00
parent f99abd660d
commit 3a0a234d50

View file

@ -6,6 +6,7 @@ package helma.util;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.text.*; import java.text.*;
import java.util.zip.GZIPOutputStream;
/** /**
* Utility class for asynchronous logging. * Utility class for asynchronous logging.
@ -20,27 +21,43 @@ public final class Logger {
// hash map of loggers // hash map of loggers
static HashMap loggerMap; static HashMap loggerMap;
private LinkedList entries; // buffer for log items
private List entries;
// fields used for logging to files
private String filename; private String filename;
private String dirname; private File logdir;
private File dir; private File logfile;
private File currentFile; private PrintWriter writer;
private PrintWriter currentWriter; // the canonical name for this logger
private int fileindex = 0; String canonicalName;
private DecimalFormat nformat;
private DateFormat dformat; // used when logging to a PrintStream such as System.out
private long dateLastRendered;
private String dateCache;
private PrintStream out = null; private PrintStream out = null;
// flag to tell runner thread if this log should be closed/discarded
boolean closed = false; boolean closed = false;
// fields for date rendering and caching
static DateFormat dformat = new SimpleDateFormat ("[yyyy/MM/dd HH:mm] ");
static long dateLastRendered;
static String dateCache;
// number format for log file rotation
DecimalFormat nformat = new DecimalFormat ("000");
DateFormat aformat = new SimpleDateFormat ("yyyy-MM-dd");
/** /**
* Create a logger for a PrintStream, such as System.out. * Create a logger for a PrintStream, such as System.out.
*/ */
public Logger (PrintStream out) { public Logger (PrintStream out) {
dformat = new SimpleDateFormat ("[yyyy/MM/dd HH:mm] ");
this.out = out; this.out = out;
entries = new LinkedList (); 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 ());
// register this instance with static logger list // register this instance with static logger list
start (this); start (this);
@ -51,20 +68,18 @@ public final class Logger {
* rotated every x bytes. * rotated every x bytes.
*/ */
private Logger (String dirname, String filename) throws IOException { private Logger (String dirname, String filename) throws IOException {
if (filename == null || dirname == null)
throw new IOException ("Logger can't use null as file or directory name");
this.filename = filename; this.filename = filename;
this.dirname = dirname; logdir = new File (dirname);
nformat = new DecimalFormat ("00000"); logfile = new File (logdir, filename+".log");
dformat = new SimpleDateFormat ("[yyyy/MM/dd HH:mm] "); canonicalName = logfile.getCanonicalPath ();
dir = new File (dirname);
if (!dir.exists()) if (!logdir.exists())
dir.mkdirs (); logdir.mkdirs ();
currentFile = new File (dir, filename+nformat.format(++fileindex)+".log"); rotateLogFile ();
while (currentFile.exists())
currentFile = new File (dir, filename+nformat.format(++fileindex)+".log"); // create a synchronized list for log entries since different threads may
currentWriter = new PrintWriter (new FileWriter (currentFile), false); // attempt to modify the list at the same time
entries = new LinkedList (); entries = Collections.synchronizedList (new LinkedList ());
// register this instance with static logger list // register this instance with static logger list
start (this); start (this);
@ -75,11 +90,13 @@ public final class Logger {
* Get a logger with a symbolic file name within a directory. * Get a logger with a symbolic file name within a directory.
*/ */
public static synchronized Logger getLogger (String dirname, String filename) throws IOException { public static synchronized Logger getLogger (String dirname, String filename) throws IOException {
File f = new File (dirname, filename); if (filename == null || dirname == null)
throw new IOException ("Logger can't use null as file or directory name");
File file = new File (dirname, filename+".log");
Logger log = null; Logger log = null;
if (loggerMap != null) if (loggerMap != null)
log = (Logger) loggerMap.get (f); log = (Logger) loggerMap.get (file.getCanonicalPath());
if (log == null) if (log == null || log.isClosed ())
log = new Logger (dirname, filename); log = new Logger (dirname, filename);
return log; return log;
} }
@ -89,13 +106,16 @@ public final class Logger {
* Append a message to the log. * Append a message to the log.
*/ */
public void log (String msg) { public void log (String msg) {
// if we are closed, drop message without further notice
if (closed)
return;
// it's enough to render the date every 15 seconds // it's enough to render the date every 15 seconds
if (System.currentTimeMillis () - 15000 > dateLastRendered) if (System.currentTimeMillis () - 15000 > dateLastRendered)
renderDate (); renderDate ();
entries.add (dateCache + msg); entries.add (dateCache + msg);
} }
private synchronized void renderDate () { private static synchronized void renderDate () {
dateLastRendered = System.currentTimeMillis (); dateLastRendered = System.currentTimeMillis ();
dateCache = dformat.format (new Date()); dateCache = dformat.format (new Date());
} }
@ -104,25 +124,34 @@ public final class Logger {
/** /**
* Return an object which identifies this logger. * Return an object which identifies this logger.
*/ */
public Object getKey () { public String getCanonicalName () {
if (dirname != null && filename != null) return canonicalName;
return new File (dirname, filename); }
return null;
/**
* Get the list of unwritten entries
*/
public List getEntries () {
return entries;
} }
/** /**
* This is called by the runner thread to perform actual IO. * This is called by the runner thread to perform actual output.
*/ */
public void run () { public void write () {
if (entries.isEmpty ()) if (entries.isEmpty ())
return; return;
try { try {
if (currentFile != null && currentFile.length() > 10000000) { if (logfile != null &&
(logfile.length() > 10000000 || !logfile.exists())) {
// rotate log files each 10 megs // rotate log files each 10 megs
swapFile (); rotateLogFile ();
} }
int l = entries.size(); int l = entries.size();
// check if writing to printstream or file
if (out != null) { if (out != null) {
for (int i=0; i<l; i++) { for (int i=0; i<l; i++) {
String entry = (String) entries.get (0); String entry = (String) entries.get (0);
@ -133,32 +162,83 @@ public final class Logger {
for (int i=0; i<l; i++) { for (int i=0; i<l; i++) {
String entry = (String) entries.get (0); String entry = (String) entries.get (0);
entries.remove (0); entries.remove (0);
currentWriter.println (entry); writer.println (entry);
} }
currentWriter.flush (); writer.flush ();
} }
} catch (Exception x) { } catch (Exception x) {
// System.err.println ("Error writing log file "+this+": "+x);
if (entries.size() > 1000) {
// more than 1000 entries queued plus exception - something
// is definitely wrong with this logger. Close and write error msg to std out.
System.err.println (entries.size()+" log entries queued in "+this+". Closing Logger.");
entries.clear ();
close ();
}
} }
} }
/** /**
* Rotata log files, closing the old file and starting a new one. * Rotate log files, closing, renaming and gzipping the old file and
* start a new one.
*/ */
private void swapFile () { private void rotateLogFile () throws IOException {
try { if (writer != null) try {
currentWriter.close(); writer.close();
currentFile = new File (dir, filename+nformat.format(++fileindex)+".log"); } catch (Exception ignore) {}
currentWriter = new PrintWriter (new FileWriter (currentFile), false); if (logfile.exists()) {
} catch (IOException iox) { String today = aformat.format(new Date());
System.err.println ("Error swapping Log files: "+iox); int ct=0;
File archive = null;
while (archive==null || archive.exists()) {
String archidx = ct>999 ? Integer.toString(ct) : nformat.format (++ct);
String archname = filename+"-"+today+"-"+ archidx +".log.gz";
archive = new File (logdir, archname);
} }
if (logfile.renameTo (archive))
(new GZipper(archive)).start();
else
System.err.println ("Error renaming old log file to "+archive);
}
writer = new PrintWriter (new FileWriter (logfile), false);
} }
/** /**
* The static start class adds a log to the list of logs and starts the * Tell whether this log is closed.
* runner thread if necessary. */
public boolean isClosed () {
return closed;
}
/**
* Tells a log to close down. Only the flag is set, the actual closing is
* done by the runner thread next time it comes around.
*/
public void close () {
this.closed = true;
}
/**
* Actually closes the file writer of a log.
*/
void closeFiles () {
if (writer != null) try {
writer.close ();
} catch (Exception ignore) {}
}
/**
* Return a string representation of this Logger
*/
public String toString () {
return "Logger["+canonicalName+"]";
}
/**
* Add a log to the list of logs and
* create and start the runner thread if necessary.
*/ */
static synchronized void start (Logger log) { static synchronized void start (Logger log) {
if (loggers == null) if (loggers == null)
@ -167,29 +247,22 @@ public final class Logger {
loggerMap = new HashMap (); loggerMap = new HashMap ();
loggers.add (log); loggers.add (log);
loggerMap.put (log.getKey (), log); loggerMap.put (log.canonicalName, log);
if (runner == null || !runner.isAlive ()) { if (runner == null || !runner.isAlive ()) {
runner = new Runner (); runner = new Runner ();
// runner.setPriority (Thread.NORM_PRIORITY-1);
runner.start (); runner.start ();
} }
} }
/**
* Tells a log to close down
*/
public void close () {
this.closed = true;
}
/** /**
* Closes the file writer of a log * Return a list of all active Loggers
*/ */
void closeFiles () { public static List getLoggers () {
if (currentWriter != null) try { if (loggers == null)
currentWriter.close (); return null;
} catch (Exception ignore) {} return (List) loggers.clone ();
} }
/** /**
@ -198,25 +271,60 @@ public final class Logger {
static class Runner extends Thread { static class Runner extends Thread {
public void run () { public void run () {
while (!isInterrupted ()) { while (runner == this && !isInterrupted ()) {
int l = loggers.size(); int nloggers = loggers.size();
for (int i=l-1; i>=0; i--) { for (int i=nloggers-1; i>=0; i--) {
try {
Logger log = (Logger) loggers.get (i); Logger log = (Logger) loggers.get (i);
log.run (); log.write ();
if (log.closed) { if (log.closed && log.entries.isEmpty()) {
loggers.remove (log); loggers.remove (log);
loggerMap.remove (log.getKey ());
log.closeFiles (); log.closeFiles ();
} }
} catch (Exception x) {
System.err.println ("Error in Logger main loop: "+x);
}
} }
try { try {
sleep (700); sleep (500);
} catch (InterruptedException ix) {} } catch (InterruptedException ix) {}
} }
} }
} }
/**
* a Thread class that zips up a file, filename will stay the same.
*/
class GZipper extends Thread {
File file, temp;
public GZipper (File file) {
this.file = file;
this.temp = new File (file.getAbsolutePath()+".tmp");
}
public void run() {
long start = System.currentTimeMillis();
try {
GZIPOutputStream zip = new GZIPOutputStream( new FileOutputStream(temp));
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
byte[] b = new byte[1024];
int len = 0;
while( (len=in.read(b,0,1024))!=-1 ) {
zip.write(b,0,len);
}
zip.close();
in.close();
file.delete();
temp.renameTo(file);
} catch ( Exception e ) {
System.err.println (e.toString());
}
}
}
/** /**
* test main method * test main method
@ -225,10 +333,11 @@ public final class Logger {
Logger log = new Logger (".", "testlog"); Logger log = new Logger (".", "testlog");
long start = System.currentTimeMillis (); long start = System.currentTimeMillis ();
for (int i=0; i<50000; i++) for (int i=0; i<50000; i++)
log.log ("test log entry aasdfasdfasdfasdf"); log.log ("test log entry "+i);
log.log ("done: "+(System.currentTimeMillis () - start)); log.log ("done: "+(System.currentTimeMillis () - start));
System.err.println (System.currentTimeMillis () - start); System.err.println (System.currentTimeMillis () - start);
System.exit (0); log.close ();
// System.exit (0);
} }
} }