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:
parent
f99abd660d
commit
3a0a234d50
1 changed files with 195 additions and 86 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue