Checking in Commons Logging support from Daniel, plus a major rewrite

of the Helma logging framework.
This commit is contained in:
hns 2003-11-28 20:15:28 +00:00
parent d8b1f178ad
commit 811410599a
9 changed files with 818 additions and 528 deletions

View file

@ -29,6 +29,9 @@ import java.lang.reflect.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.rmi.*; import java.rmi.*;
import java.util.*; import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** /**
* The central class of a Helma application. This class keeps a pool of so-called * The central class of a Helma application. This class keeps a pool of so-called
@ -46,13 +49,13 @@ public final class Application implements IPathElement, Runnable {
// properties and db-properties // properties and db-properties
SystemProperties dbProps; SystemProperties dbProps;
// home, app and data directories // Helma server home directory
File home; File home;
// home, app and data directories // application directory
File appDir; File appDir;
// home, app and data directories // embedded db directory
File dbDir; File dbDir;
// this application's node manager // this application's node manager
@ -96,11 +99,13 @@ public final class Application implements IPathElement, Runnable {
// Map of requesttrans -> active requestevaluators // Map of requesttrans -> active requestevaluators
Hashtable activeRequests; Hashtable activeRequests;
// Two logs for each application: events and accesses // Two logs for each application
Logger eventLog; Log eventLog;
Log accessLog;
// Two logs for each application: events and accesses // Symbolic names for each log
Logger accessLog; String eventLogName;
String accessLogName;
// A transient node that is shared among all evaluators // A transient node that is shared among all evaluators
protected INode cachenode; protected INode cachenode;
@ -229,6 +234,10 @@ public final class Application implements IPathElement, Runnable {
props = new SystemProperties(propfile.getAbsolutePath(), sysProps); props = new SystemProperties(propfile.getAbsolutePath(), sysProps);
// get log names
accessLogName = props.getProperty("accessLog", "helma."+name+".access");
eventLogName = props.getProperty("eventLog", "helma."+name+".event");
// create app-level db sources // create app-level db sources
File dbpropfile = new File(appDir, "db.properties"); File dbpropfile = new File(appDir, "db.properties");
@ -266,6 +275,16 @@ public final class Application implements IPathElement, Runnable {
*/ */
public void init() public void init()
throws DatabaseException, ScriptingException, MalformedURLException { throws DatabaseException, ScriptingException, MalformedURLException {
// create and init type mananger
typemgr = new TypeManager(this);
typemgr.createPrototypes();
// set the context classloader. Note that this must be done before
// using the logging framework so that a new LogFactory gets created
// for this app.
Thread.currentThread().setContextClassLoader(typemgr.loader);
if (Server.getServer() != null) { if (Server.getServer() != null) {
Vector extensions = Server.getServer().getExtensions(); Vector extensions = Server.getServer().getExtensions();
@ -286,10 +305,6 @@ public final class Application implements IPathElement, Runnable {
loadSessionData(null); loadSessionData(null);
} }
// create and init type mananger
typemgr = new TypeManager(this);
typemgr.createPrototypes();
// create and init evaluator/thread lists // create and init evaluator/thread lists
freeThreads = new Stack(); freeThreads = new Stack();
allThreads = new Vector(); allThreads = new Vector();
@ -317,11 +332,14 @@ public final class Application implements IPathElement, Runnable {
activeCronJobs = new WeakHashMap(); activeCronJobs = new WeakHashMap();
customCronJobs = new Hashtable(); customCronJobs = new Hashtable();
// create the skin manager
skinmgr = new SkinManager(this); skinmgr = new SkinManager(this);
rootMapping = getDbMapping("root"); rootMapping = getDbMapping("root");
userMapping = getDbMapping("user"); userMapping = getDbMapping("user");
// The whole user/userroot handling is basically old
// ugly obsolete crap. Don't bother.
SystemProperties p = new SystemProperties(); SystemProperties p = new SystemProperties();
String usernameField = userMapping.getNameField(); String usernameField = userMapping.getNameField();
@ -334,7 +352,12 @@ public final class Application implements IPathElement, Runnable {
userRootMapping = new DbMapping(this, "__userroot__", p); userRootMapping = new DbMapping(this, "__userroot__", p);
userRootMapping.update(); userRootMapping.update();
// create the node manager
nmgr = new NodeManager(this, dbDir.getAbsolutePath(), props); nmgr = new NodeManager(this, dbDir.getAbsolutePath(), props);
// reset the classloader to the parent/system/server classloader.
Thread.currentThread().setContextClassLoader(typemgr.loader.getParent());
} }
/** /**
@ -399,14 +422,6 @@ public final class Application implements IPathElement, Runnable {
storeSessionData(null); storeSessionData(null);
} }
// stop logs if they exist
if (eventLog != null) {
eventLog.close();
}
if (accessLog != null) {
accessLog.close();
}
} }
/** /**
@ -1266,52 +1281,43 @@ public final class Application implements IPathElement, Runnable {
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
/** /**
* Get the logger object for logging generic events * Log a generic application event
*/ */
public void logEvent(String msg) { public void logEvent(String msg) {
if ((eventLog == null) || eventLog.isClosed()) { if (eventLog == null) {
eventLog = getLogger("event"); eventLog = getLogger(eventLogName);
} }
eventLog.log(msg); eventLog.info(msg);
} }
/** /**
* Get the logger object for logging access events * Log an application access
*/ */
public void logAccess(String msg) { public void logAccess(String msg) {
if ((accessLog == null) || accessLog.isClosed()) { if (accessLog == null) {
accessLog = getLogger("access"); accessLog = getLogger(accessLogName);
} }
accessLog.log(msg); accessLog.info(msg);
} }
/** /**
* Get a logger object to log events for this application. * Get a logger object to log events for this application.
*/ */
public Logger getLogger(String logname) { protected Log getLogger(String logname) {
Logger log = null; String logdir = props.getProperty("logdir");
String logDir = props.getProperty("logdir", "log");
// allow log to be redirected to System.out by setting logdir to "console" if ("console".equals(logdir) || "console".equals(logname)) {
if ("console".equalsIgnoreCase(logDir)) { return Logging.getConsoleLog();
return new Logger(System.out); } else {
// set up helma.logdir system property in case we're using it
File dir = new File(logdir);
System.setProperty("helma.logdir", dir.getAbsolutePath());
return LogFactory.getLog(logname);
} }
File helper = new File(logDir);
// construct the fully qualified log name
String fullLogname = name + "_" + logname;
if ((home != null) && !helper.isAbsolute()) {
helper = new File(home, logDir);
}
logDir = helper.getAbsolutePath();
log = Logger.getLogger(logDir, fullLogname);
return log;
} }
/** /**

View file

@ -72,7 +72,7 @@ public class ApplicationBean implements Serializable {
public void log(String logname, Object msg) { public void log(String logname, Object msg) {
String str = (msg == null) ? "null" : msg.toString(); String str = (msg == null) ? "null" : msg.toString();
app.getLogger(logname).log(str); app.getLogger(logname).info(str);
} }
/** /**
@ -98,7 +98,7 @@ public class ApplicationBean implements Serializable {
if (app.debug()) { if (app.debug()) {
String str = (msg == null) ? "null" : msg.toString(); String str = (msg == null) ? "null" : msg.toString();
app.getLogger(logname).log(str); app.getLogger(logname).info(str);
} }
} }

View file

@ -19,6 +19,8 @@ package helma.main;
import helma.framework.core.*; import helma.framework.core.*;
import helma.util.StringUtils; import helma.util.StringUtils;
import helma.util.SystemProperties; import helma.util.SystemProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlrpc.XmlRpcHandler; import org.apache.xmlrpc.XmlRpcHandler;
import org.mortbay.http.*; import org.mortbay.http.*;
import org.mortbay.http.handler.*; import org.mortbay.http.handler.*;
@ -97,7 +99,7 @@ public class ApplicationManager implements XmlRpcHandler {
} }
} }
} catch (Exception mx) { } catch (Exception mx) {
Server.getLogger().log("Error checking applications: " + mx); Server.getLogger().error("Error checking applications: " + mx);
} }
lastModified = System.currentTimeMillis(); lastModified = System.currentTimeMillis();
@ -161,7 +163,7 @@ public class ApplicationManager implements XmlRpcHandler {
lastModified = System.currentTimeMillis(); lastModified = System.currentTimeMillis();
} catch (Exception mx) { } catch (Exception mx) {
Server.getLogger().log("Error starting applications: " + mx); Server.getLogger().error("Error starting applications: " + mx);
mx.printStackTrace(); mx.printStackTrace();
} }
} }
@ -322,7 +324,7 @@ public class ApplicationManager implements XmlRpcHandler {
void start() { void start() {
Server.getLogger().log("Building application " + appName); Server.getLogger().info("Building application " + appName);
try { try {
// create the application instance // create the application instance
@ -336,13 +338,13 @@ public class ApplicationManager implements XmlRpcHandler {
app.init(); app.init();
app.start(); app.start();
} catch (Exception x) { } catch (Exception x) {
Server.getLogger().log("Error creating application " + appName + ": " + x); Server.getLogger().error("Error creating application " + appName + ": " + x);
x.printStackTrace(); x.printStackTrace();
} }
} }
void stop() { void stop() {
Server.getLogger().log("Stopping application " + appName); Server.getLogger().info("Stopping application " + appName);
// unbind application // unbind application
unbind(); unbind();
@ -350,9 +352,9 @@ public class ApplicationManager implements XmlRpcHandler {
// stop application // stop application
try { try {
app.stop(); app.stop();
Server.getLogger().log("Stopped application " + appName); Server.getLogger().info("Stopped application " + appName);
} catch (Exception x) { } catch (Exception x) {
Server.getLogger().log("Couldn't stop app: " + x); Server.getLogger().error("Couldn't stop app: " + x);
} }
descriptors.remove(appName); descriptors.remove(appName);
@ -361,7 +363,7 @@ public class ApplicationManager implements XmlRpcHandler {
void bind() { void bind() {
try { try {
Server.getLogger().log("Binding application " + appName); Server.getLogger().info("Binding application " + appName);
// bind to RMI server // bind to RMI server
if (port > 0) { if (port > 0) {
@ -380,7 +382,7 @@ public class ApplicationManager implements XmlRpcHandler {
if (encode) { if (encode) {
// FIXME: ContentEncodingHandler is broken/removed in Jetty 4.2 // FIXME: ContentEncodingHandler is broken/removed in Jetty 4.2
// context.addHandler(new ContentEncodingHandler()); // context.addHandler(new ContentEncodingHandler());
Server.getLogger().log("Warning: disabling response encoding for Jetty 4.2 compatibility"); Server.getLogger().warn("Warning: disabling response encoding for Jetty 4.2 compatibility");
} }
ServletHandler handler = new ServletHandler(); ServletHandler handler = new ServletHandler();
@ -413,7 +415,7 @@ public class ApplicationManager implements XmlRpcHandler {
protectedContent = new File(server.getHopHome(), protectedStaticDir); protectedContent = new File(server.getHopHome(), protectedStaticDir);
} }
context.setResourceBase(protectedContent.getAbsolutePath()); context.setResourceBase(protectedContent.getAbsolutePath());
Server.getLogger().log("Serving protected static from " + Server.getLogger().info("Serving protected static from " +
protectedContent.getAbsolutePath()); protectedContent.getAbsolutePath());
context.addHandler(new ResourceHandler()); context.addHandler(new ResourceHandler());
} }
@ -428,9 +430,9 @@ public class ApplicationManager implements XmlRpcHandler {
staticContent = new File(server.getHopHome(), staticDir); staticContent = new File(server.getHopHome(), staticDir);
} }
Server.getLogger().log("Serving static from " + Server.getLogger().info("Serving static from " +
staticContent.getAbsolutePath()); staticContent.getAbsolutePath());
Server.getLogger().log("Mounting static at " + Server.getLogger().info("Mounting static at " +
staticMountpoint); staticMountpoint);
context = server.http.addContext(staticMountpoint); context = server.http.addContext(staticMountpoint);
@ -450,13 +452,13 @@ public class ApplicationManager implements XmlRpcHandler {
xmlrpcHandlers.put(xmlrpcHandlerName, app); xmlrpcHandlers.put(xmlrpcHandlerName, app);
// app.start(); // app.start();
} catch (Exception x) { } catch (Exception x) {
Server.getLogger().log("Couldn't bind app: " + x); Server.getLogger().error("Couldn't bind app: " + x);
x.printStackTrace(); x.printStackTrace();
} }
} }
void unbind() { void unbind() {
Server.getLogger().log("Unbinding application " + appName); Server.getLogger().info("Unbinding application " + appName);
try { try {
// unbind from RMI server // unbind from RMI server
@ -486,10 +488,14 @@ public class ApplicationManager implements XmlRpcHandler {
// unregister as XML-RPC handler // unregister as XML-RPC handler
xmlrpcHandlers.remove(xmlrpcHandlerName); xmlrpcHandlers.remove(xmlrpcHandlerName);
} catch (Exception x) { } catch (Exception x) {
Server.getLogger().log("Couldn't unbind app: " + x); Server.getLogger().error("Couldn't unbind app: " + x);
} }
} }
public String toString() {
return "[AppDescriptor "+app+"]";
}
} }
} }

View file

@ -16,8 +16,9 @@
package helma.main; package helma.main;
import helma.util.Logger; import helma.util.*;
import java.util.List; import java.util.List;
import org.apache.commons.logging.LogFactory;
/** /**
* ShutdownHook that shuts down all running Helma applications on exit. * ShutdownHook that shuts down all running Helma applications on exit.
@ -40,20 +41,12 @@ public class HelmaShutdownHook extends Thread {
public void run() { public void run() {
System.err.println("Shutting down Helma - please stand by..."); System.err.println("Shutting down Helma - please stand by...");
Logger logger = Server.getLogger(); LogFactory.getLog("helma.server").info("Shutting down Helma");
if (logger != null) {
logger.log("Shutting down Helma");
}
appmgr.stopAll(); appmgr.stopAll();
List loggers = Logger.getLoggers(); if (LogFactory.getFactory() instanceof Logging) {
int l = loggers.size(); Logging.shutdown();
}
for (int i = 0; i < l; i++) }
((Logger) loggers.get(i)).close();
Logger.wakeup();
}
} }

View file

@ -45,7 +45,7 @@ public class HelmaSocketFactory extends RMISocketFactory {
try { try {
filter.addAddress(address); filter.addAddress(address);
} catch (IOException x) { } catch (IOException x) {
Server.getLogger().log("Could not add " + address + Server.getLogger().error("Could not add " + address +
" to Socket Filter: invalid address."); " to Socket Filter: invalid address.");
} }
} }

View file

@ -21,10 +21,15 @@ import helma.framework.*;
import helma.framework.core.*; import helma.framework.core.*;
import helma.objectmodel.db.DbSource; import helma.objectmodel.db.DbSource;
import helma.util.*; import helma.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlrpc.*; import org.apache.xmlrpc.*;
import org.mortbay.http.*; import org.mortbay.http.*;
import org.mortbay.http.ajp.*; import org.mortbay.http.ajp.*;
import org.mortbay.util.*; import org.mortbay.util.InetAddrPort;
import org.mortbay.util.LogSink;
import org.mortbay.util.MultiException;
import org.mortbay.util.Frame;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.rmi.registry.*; import java.rmi.registry.*;
@ -47,10 +52,14 @@ public class Server implements IPathElement, Runnable {
protected static File hopHome = null; protected static File hopHome = null;
// our logger // our logger
private static Logger logger; private static Log logger;
// are we using helma.util.Logging?
private static boolean helmaLogging;
// server start time
public final long starttime; public final long starttime;
// if true we only accept RMI and XML-RPC connections from // if true we only accept RMI and XML-RPC connections from
// explicitly listed hosts. // explicitly listed hosts.
public boolean paranoid; public boolean paranoid;
private ApplicationManager appManager; private ApplicationManager appManager;
@ -143,6 +152,13 @@ public class Server implements IPathElement, Runnable {
// create system properties // create system properties
sysProps = new SystemProperties(propfile); sysProps = new SystemProperties(propfile);
// set the log factory property
String logFactory = sysProps.getProperty("loggerFactory",
"helma.util.Logging");
helmaLogging = "helma.util.Logging".equals(logFactory);
System.setProperty("org.apache.commons.logging.LogFactory", logFactory);
// check if there's a property setting for those ports not specified via command line // check if there's a property setting for those ports not specified via command line
if ((websrvPort == null) && (sysProps.getProperty("webPort") != null)) { if ((websrvPort == null) && (sysProps.getProperty("webPort") != null)) {
try { try {
@ -243,7 +259,7 @@ public class Server implements IPathElement, Runnable {
try { try {
hopHome = hopHome.getCanonicalFile(); hopHome = hopHome.getCanonicalFile();
} catch (IOException iox) { } catch (IOException iox) {
System.err.println("Error calling getCanonicalFile() on hopHome: " + iox); hopHome = hopHome.getAbsoluteFile();
} }
// set the current working directory to the helma home dir. // set the current working directory to the helma home dir.
@ -254,23 +270,23 @@ public class Server implements IPathElement, Runnable {
System.setProperty("user.dir", hopHome.getPath()); System.setProperty("user.dir", hopHome.getPath());
// from now on it's safe to call getLogger() because hopHome is set up // from now on it's safe to call getLogger() because hopHome is set up
getLogger();
String startMessage = "Starting Helma " + version + " on Java " + String startMessage = "Starting Helma " + version + " on Java " +
System.getProperty("java.version"); System.getProperty("java.version");
getLogger().log(startMessage); logger.info(startMessage);
// also print a msg to System.out // also print a msg to System.out
System.out.println(startMessage); System.out.println(startMessage);
getLogger().log("propfile = " + propfile); logger.info("Setting Helma Home to " + hopHome);
getLogger().log("hopHome = " + hopHome);
File helper = new File(hopHome, "db.properties"); File helper = new File(hopHome, "db.properties");
dbPropfile = helper.getAbsolutePath(); dbPropfile = helper.getAbsolutePath();
dbProps = new SystemProperties(dbPropfile); dbProps = new SystemProperties(dbPropfile);
DbSource.setDefaultProps(dbProps); DbSource.setDefaultProps(dbProps);
getLogger().log("dbPropfile = " + dbPropfile);
appsPropfile = sysProps.getProperty("appsPropFile"); appsPropfile = sysProps.getProperty("appsPropFile");
@ -282,7 +298,6 @@ public class Server implements IPathElement, Runnable {
appsPropfile = helper.getAbsolutePath(); appsPropfile = helper.getAbsolutePath();
appsProps = new SystemProperties(appsPropfile); appsProps = new SystemProperties(appsPropfile);
getLogger().log("appsPropfile = " + appsPropfile);
paranoid = "true".equalsIgnoreCase(sysProps.getProperty("paranoid")); paranoid = "true".equalsIgnoreCase(sysProps.getProperty("paranoid"));
@ -298,9 +313,9 @@ public class Server implements IPathElement, Runnable {
TimeZone.setDefault(TimeZone.getTimeZone(timezone)); TimeZone.setDefault(TimeZone.getTimeZone(timezone));
} }
getLogger().log("Locale = " + Locale.getDefault()); // logger.debug("Locale = " + Locale.getDefault());
getLogger().log("TimeZone = " + // logger.debug("TimeZone = " +
TimeZone.getDefault().getDisplayName(Locale.getDefault())); // TimeZone.getDefault().getDisplayName(Locale.getDefault()));
dbSources = new Hashtable(); dbSources = new Hashtable();
@ -320,9 +335,9 @@ public class Server implements IPathElement, Runnable {
ext.init(this); ext.init(this);
extensions.add(ext); extensions.add(ext);
getLogger().log("Loaded: " + extClassName); logger.info("Loaded: " + extClassName);
} catch (Throwable e) { } catch (Throwable e) {
getLogger().log("Error loading extension " + extClassName + ": " + e.toString()); logger.error("Error loading extension " + extClassName + ": " + e.toString());
} }
} }
} }
@ -379,8 +394,8 @@ public class Server implements IPathElement, Runnable {
// disable Jetty logging FIXME: seems to be a jetty bug; as soon // disable Jetty logging FIXME: seems to be a jetty bug; as soon
// as the logging is disabled, the more is logged // as the logging is disabled, the more is logged
Log.instance().disableLog(); org.mortbay.util.Log.instance().disableLog();
Log.instance().add(new LogSink() { org.mortbay.util.Log.instance().add(new LogSink() {
public String getOptions() { public String getOptions() {
return null; return null;
} }
@ -434,7 +449,7 @@ public class Server implements IPathElement, Runnable {
} }
ajp13.setRemoteServers(jkallowarr); ajp13.setRemoteServers(jkallowarr);
getLogger().log("Starting AJP13-Listener on port " + (ajp13Port)); logger.info("Starting AJP13-Listener on port " + (ajp13Port));
} }
if (xmlrpcPort != null) { if (xmlrpcPort != null) {
@ -463,7 +478,7 @@ public class Server implements IPathElement, Runnable {
} }
} }
getLogger().log("Starting XML-RPC server on port " + (xmlrpcPort)); logger.info("Starting XML-RPC server on port " + (xmlrpcPort));
} }
if (rmiPort != null) { if (rmiPort != null) {
@ -484,7 +499,7 @@ public class Server implements IPathElement, Runnable {
RMISocketFactory.setSocketFactory(factory); RMISocketFactory.setSocketFactory(factory);
} }
getLogger().log("Starting RMI server on port " + rmiPort); logger.info("Starting RMI server on port " + rmiPort);
LocateRegistry.createRegistry(rmiPort.getPort()); LocateRegistry.createRegistry(rmiPort.getPort());
// create application manager which binds to the given RMI port // create application manager which binds to the given RMI port
@ -501,7 +516,7 @@ public class Server implements IPathElement, Runnable {
// add shutdown hook to close running apps and servers on exit // add shutdown hook to close running apps and servers on exit
Runtime.getRuntime().addShutdownHook(new HelmaShutdownHook(appManager)); Runtime.getRuntime().addShutdownHook(new HelmaShutdownHook(appManager));
} catch (Exception gx) { } catch (Exception gx) {
getLogger().log("Error initializing embedded database: " + gx); logger.error("Error initializing embedded database: " + gx);
gx.printStackTrace(); gx.printStackTrace();
return; return;
@ -517,10 +532,10 @@ public class Server implements IPathElement, Runnable {
.newInstance(); .newInstance();
System.setSecurityManager(secMan); System.setSecurityManager(secMan);
getLogger().log("Setting security manager to " + secManClass); logger.info("Setting security manager to " + secManClass);
} }
} catch (Exception x) { } catch (Exception x) {
getLogger().log("Error setting security manager: " + x); logger.error("Error setting security manager: " + x);
} }
// start applications // start applications
@ -531,7 +546,7 @@ public class Server implements IPathElement, Runnable {
try { try {
http.start(); http.start();
} catch (MultiException m) { } catch (MultiException m) {
getLogger().log("Error starting embedded web server: " + m); logger.error("Error starting embedded web server: " + m);
} }
} }
@ -539,7 +554,7 @@ public class Server implements IPathElement, Runnable {
try { try {
ajp13.start(); ajp13.start();
} catch (Exception m) { } catch (Exception m) {
getLogger().log("Error starting AJP13 listener: " + m); logger.error("Error starting AJP13 listener: " + m);
} }
} }
@ -552,7 +567,7 @@ public class Server implements IPathElement, Runnable {
try { try {
appManager.checkForChanges(); appManager.checkForChanges();
} catch (Exception x) { } catch (Exception x) {
getLogger().log("Caught in app manager loop: " + x); logger.warn("Caught in app manager loop: " + x);
} }
} }
} }
@ -574,24 +589,25 @@ public class Server implements IPathElement, Runnable {
/** /**
* Get a logger to use for output in this server. * Get a logger to use for output in this server.
*/ */
public static Logger getLogger() { public static Log getLogger() {
if (logger == null) { if (logger == null) {
// get the logDir property
String logDir = sysProps.getProperty("logdir", "log"); String logDir = sysProps.getProperty("logdir", "log");
if ("console".equalsIgnoreCase(logDir)) { if ("console".equals(logDir)) {
logger = new Logger(System.out); logger = Logging.getConsoleLog();
} else { } else {
File helper = new File(logDir); if (helmaLogging) {
// set up helma.logdir system property
if ((hopHome != null) && !helper.isAbsolute()) { File dir = new File(logDir);
helper = new File(hopHome, logDir); if (!dir.isAbsolute()) {
dir = new File(hopHome, logDir);
}
System.setProperty("helma.logdir", dir.getAbsolutePath());
} }
logger = LogFactory.getLog("helma.server");
logDir = helper.getAbsolutePath();
logger = Logger.getLogger(logDir, "hop");
} }
} }
return logger; return logger;
} }

View file

@ -0,0 +1,291 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.util;
import org.apache.commons.logging.Log;
import java.io.*;
import java.text.*;
import java.util.*;
import java.util.zip.GZIPOutputStream;
/**
* An extended Logger that writes to a file and rotates files each midnight.
*
* Note that the methods in this class that operate on the file writer
* (write(), openFile(), closeFile()) are expected to be called by just one
* thread (the static runner thread in helma.util.Logging) which is why they
* are not synchronized, although they're not threadsafe.
*
* @author Stefan Pollack
* @author Daniel Ruthardt
* @author Hannes Wallnoefer
*/
public class FileLogger extends Logger implements Log {
// fields used for logging to files
private String name;
private File logdir;
private File logfile;
// number format for log file rotation
DecimalFormat nformat = new DecimalFormat("000");
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
* rotated every x bytes.
*/
protected FileLogger(String directory, String name) {
this.name = name;
logdir = new File(directory);
logfile = new File(logdir, name + ".log");
if (!logdir.exists()) {
logdir.mkdirs();
}
openFile();
// 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
* or it will return a writer that appends to an existing file.
*/
private void openFile() {
try {
if (logfile.exists() && (logfile.lastModified() < lastMidnight())) {
// rotate if a log file exists and is NOT from today
rotateLogFile();
} else {
// create a new log file, appending to an existing file
writer = new PrintWriter(new FileWriter(logfile.getAbsolutePath(), true),
false);
}
} catch (IOException iox) {
System.err.println("Error creating log " + name + ": " + iox);
}
}
/**
* Actually closes the file writer of a log.
*/
void closeFile() {
if (writer != null) {
try {
writer.close();
} catch (Exception ignore) {
} finally {
writer = null;
}
}
}
/**
* Return true if we have a file writer open
*/
boolean isOpen() {
return writer != null;
}
/**
* This is called by the runner thread to perform actual output.
*/
void write() {
if (entries.isEmpty()) {
return;
}
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, renaming and gzipping the old file and
* start a new one.
*/
protected void rotateLogFile() throws IOException {
// if the logger is not file based do nothing.
if (logfile == null) {
return;
}
if (writer != null) {
try {
writer.close();
} catch (Exception ignore) {
}
}
// only backup/rotate if the log file is not empty,
if (logfile.exists() && (logfile.length() > 0)) {
String today = aformat.format(new Date());
int ct = 0;
File archive = null;
// first append just the date
String archname = name + "-" + today + ".log.gz";
while ((archive == null) || archive.exists()) {
archive = new File(logdir, archname);
// for the next try we append a counter
String archidx = (ct > 999) ? Integer.toString(ct) : nformat.format(++ct);
archname = name + "-" + today + "-" + archidx + ".log.gz";
}
if (logfile.renameTo(archive)) {
(new GZipper(archive)).start();
} else {
System.err.println("Error rotating log file " + canonicalName +
". Old file will possibly be overwritten!");
}
}
writer = new PrintWriter(new FileWriter(logfile), false);
}
/**
* Return a string representation of this Logger
*/
public String toString() {
return "FileLogger[" + name + "]";
}
/**
* Return an object which identifies this logger.
*/
public String getName() {
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;
}
public void error(Object parm1) {
System.err.println("Error: "+parm1);
info(parm1);
}
public void error(Object parm1, Throwable parm2) {
System.err.println("Error: "+parm1);
System.err.println("See "+logfile+" for stack trace");
info(parm1, parm2);
}
public void fatal(Object parm1) {
System.err.println("Fatal error: "+parm1);
info(parm1);
}
public void fatal(Object parm1, Throwable parm2) {
System.err.println("Fatal error: "+parm1);
System.err.println("See "+logfile+" for stack trace");
info(parm1, parm2);
}
/**
* a Thread class that zips up a file, filename will stay the same.
*/
class GZipper extends Thread {
File file;
File temp;
public GZipper(File file) {
this.file = file;
this.temp = new File(file.getAbsolutePath() + ".tmp");
}
public void run() {
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());
}
}
}
}

View file

@ -16,287 +16,46 @@
package helma.util; package helma.util;
import org.apache.commons.logging.Log;
import java.io.*; import java.io.*;
import java.text.*; import java.text.*;
import java.util.*; import java.util.*;
import java.util.zip.GZIPOutputStream;
/** /**
* Utility class for asynchronous logging. * A simple logger that writes to a PrintStream such as System.out.
*/ */
public final class Logger { public class Logger implements Log {
// we use one static thread for all Loggers
static Runner runner;
// the list of active loggers
static ArrayList loggers;
// hash map of loggers
static HashMap loggerMap;
// fields for date rendering and caching
static DateFormat dformat = new SimpleDateFormat("[yyyy/MM/dd HH:mm] ");
static long dateLastRendered;
static String dateCache;
// buffer for log items // buffer for log items
private List entries; List entries;
// fields used for logging to files // Writer for log output
private String filename; PrintWriter writer;
private File logdir;
private File logfile;
private PrintWriter writer;
// the canonical name for this logger // the canonical name for this logger
String canonicalName; String canonicalName;
// used when logging to a PrintStream such as System.out // fields for date rendering and caching
private PrintStream out = null; static DateFormat dformat = new SimpleDateFormat("[yyyy/MM/dd HH:mm:ss] ");
static long dateLastRendered;
static String dateCache;
// flag to tell runner thread if this log should be closed/discarded /**
boolean closed = false; * zero argument constructor, only here for FileLogger subclass
*/
// number format for log file rotation Logger() {}
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) { protected Logger(PrintStream out) {
this.out = out; writer = new PrintWriter(out);
canonicalName = out.toString(); canonicalName = out.toString();
// create a synchronized list for log entries since different threads may // create a synchronized list for log entries since different threads may
// attempt to modify the list at the same time // attempt to modify the list at the same time
entries = Collections.synchronizedList(new LinkedList()); entries = Collections.synchronizedList(new LinkedList());
// register this instance with static logger list
start(this);
}
/**
* Create a file logger. The actual file names do have numbers appended and are
* rotated every x bytes.
*/
private Logger(String dirname, String filename) {
this.filename = filename;
logdir = new File(dirname);
logfile = new File(logdir, filename + ".log");
try {
canonicalName = logfile.getCanonicalPath();
} catch (IOException iox) {
canonicalName = logfile.getAbsolutePath();
}
if (!logdir.exists()) {
logdir.mkdirs();
}
try {
if (logfile.exists() && (logfile.lastModified() < lastMidnight())) {
// rotate if a log file exists and is NOT from today
rotateLogFile();
} else {
// create a new log file, append to an existing file
writer = new PrintWriter(new FileWriter(logfile.getAbsolutePath(), true),
false);
}
} catch (IOException iox) {
System.err.println("Error creating log " + canonicalName + ": " + iox);
}
// 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
start(this);
}
/**
* Get a logger with a symbolic file name within a directory.
*/
public static synchronized Logger getLogger(String dirname, String filename) {
if ((filename == null) || (dirname == null)) {
throw new RuntimeException("Logger can't use null as file or directory name");
}
File file = new File(dirname, filename + ".log");
Logger log = null;
if (loggerMap != null) {
try {
log = (Logger) loggerMap.get(file.getCanonicalPath());
} catch (IOException iox) {
log = (Logger) loggerMap.get(file.getAbsolutePath());
}
}
if ((log == null) || log.isClosed()) {
log = new Logger(dirname, filename);
}
return log;
}
/**
* Append a message to the log.
*/
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 5 seconds
if ((System.currentTimeMillis() - 5000) > dateLastRendered) {
renderDate();
}
entries.add(dateCache + msg);
}
private static synchronized void renderDate() {
dateLastRendered = System.currentTimeMillis();
dateCache = dformat.format(new Date());
}
/**
* Return an object which identifies this logger.
*/
public String getCanonicalName() {
return canonicalName;
}
/**
* Get the list of unwritten entries
*/
public List getEntries() {
return entries;
}
/**
* This is called by the runner thread to perform actual output.
*/
public void write() {
if (entries.isEmpty()) {
return;
}
try {
int l = entries.size();
// check if writing to printstream or file
if (out != null) {
for (int i = 0; i < l; i++) {
String entry = (String) entries.get(0);
entries.remove(0);
out.println(entry);
}
} else {
if ((writer == null) || !logfile.exists() || !logfile.canWrite()) {
// rotate the log file if we can't write to it
rotateLogFile();
}
for (int i = 0; i < l; i++) {
String entry = (String) entries.get(0);
entries.remove(0);
writer.println(entry);
}
writer.flush();
}
} catch (Exception x) {
int e = entries.size();
if (e > 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 " + e + " log entries.");
entries.clear();
}
}
}
/**
* Rotate log files, closing, renaming and gzipping the old file and
* start a new one.
*/
private void rotateLogFile() throws IOException {
// if the logger is not file based do nothing.
if (logfile == null) {
return;
}
if (writer != null) {
try {
writer.close();
} catch (Exception ignore) {
}
}
// only backup/rotate if the log file is not empty,
if (logfile.exists() && (logfile.length() > 0)) {
String today = aformat.format(new Date());
int ct = 0;
File archive = null;
// first append just the date
String archname = filename + "-" + today + ".log.gz";
while ((archive == null) || archive.exists()) {
archive = new File(logdir, archname);
// for the next try we append a counter
String archidx = (ct > 999) ? Integer.toString(ct) : nformat.format(++ct);
archname = filename + "-" + today + "-" + archidx + ".log.gz";
}
if (logfile.renameTo(archive)) {
(new GZipper(archive)).start();
} else {
System.err.println("Error rotating log file " + canonicalName +
". Old file will possibly be overwritten!");
}
}
writer = new PrintWriter(new FileWriter(logfile), false);
}
/**
* Tell whether this log is closed.
*/
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) {
}
}
} }
/** /**
@ -307,187 +66,134 @@ public final class Logger {
} }
/** /**
* Add a log to the list of logs and * Return an object which identifies this logger.
* create and start the runner thread if necessary.
*/ */
static synchronized void start(Logger log) { public String getCanonicalName() {
if (loggers == null) { return canonicalName;
loggers = new ArrayList();
}
if (loggerMap == null) {
loggerMap = new HashMap();
}
loggers.add(log);
loggerMap.put(log.canonicalName, log);
if ((runner == null) || !runner.isAlive()) {
runner = new Runner();
runner.start();
}
} }
/** /**
* Return a list of all active Loggers * Append a message to the log.
*/ */
public static List getLoggers() { public void log(String msg) {
if (loggers == null) { // it's enough to render the date every second
return null; if ((System.currentTimeMillis() - 1000) > dateLastRendered) {
renderDate();
} }
return (List) loggers.clone(); entries.add(dateCache + msg);
} }
/** /**
* Notify the runner thread that it should wake up and run. * This is called by the runner thread to perform actual output.
*/ */
public static void wakeup() { void write() {
if (runner != null) { if (entries.isEmpty()) {
runner.wakeup(); return;
} }
}
private static void rotateAllLogs() { try {
int nloggers = loggers.size(); int l = entries.size();
for (int i = nloggers - 1; i >= 0; i--) { for (int i = 0; i < l; i++) {
Logger log = (Logger) loggers.get(i); String entry = (String) entries.remove(0);
writer.println(entry);
}
try { writer.flush();
log.rotateLogFile();
} catch (IOException io) { } catch (Exception x) {
System.err.println("Error rotating log " + log.getCanonicalName() + ": " + int size = entries.size();
io.toString());
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();
} }
} }
} }
/** // Pre-render the date to use for log messages. Called every 2 seconds or so.
* static synchronized void renderDate() {
* dateLastRendered = System.currentTimeMillis();
* @return ... dateCache = dformat.format(new Date());
*/
public static long nextMidnight() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, 1 + cal.get(Calendar.DATE));
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 1);
// for testing, rotate the logs every minute:
// cal.set (Calendar.MINUTE, 1 + cal.get(Calendar.MINUTE));
return cal.getTime().getTime();
} }
/** // methods to implement org.apache.commons.logging.Log interface
*
* public boolean isDebugEnabled() {
* @return ... return true;
*/
public static long lastMidnight() {
return nextMidnight() - 86400000;
} }
/** public boolean isErrorEnabled() {
* test main method return true;
*/
public static void main(String[] args) {
Logger log = new Logger(".", "testlog");
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
log.log("test log entry " + i);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
}
log.log("done: " + (System.currentTimeMillis() - start));
System.err.println(System.currentTimeMillis() - start);
log.close();
} }
/** public boolean isFatalEnabled() {
* The static runner class that loops through all loggers. return true;
*/
static class Runner extends Thread {
public synchronized void run() {
long nextMidnight = nextMidnight();
while ((runner == this) && !isInterrupted()) {
if (nextMidnight < System.currentTimeMillis()) {
rotateAllLogs();
nextMidnight = nextMidnight();
}
int nloggers = loggers.size();
for (int i = nloggers - 1; i >= 0; i--) {
try {
Logger log = (Logger) loggers.get(i);
log.write();
if (log.closed && log.entries.isEmpty()) {
loggers.remove(log);
log.closeFiles();
}
} catch (Exception x) {
System.err.println("Error in Logger main loop: " + x);
}
}
// if there are no active logs, exit logger thread
if (loggers.size() == 0) {
return;
}
try {
wait(250);
} catch (InterruptedException ix) {
}
}
}
public synchronized void wakeup() {
notifyAll();
}
} }
/** public boolean isInfoEnabled() {
* a Thread class that zips up a file, filename will stay the same. return true;
*/
class GZipper extends Thread {
File file;
File temp;
public GZipper(File file) {
this.file = file;
this.temp = new File(file.getAbsolutePath() + ".tmp");
}
public void run() {
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());
}
}
} }
public boolean isTraceEnabled() {
return true;
}
public boolean isWarnEnabled() {
return true;
}
public void trace(Object parm1) {
info(parm1);
}
public void trace(Object parm1, Throwable parm2) {
info(parm1, parm2);
}
public void debug(Object parm1) {
info(parm1);
}
public void debug(Object parm1, Throwable parm2) {
info(parm1, parm2);
}
public void info(Object parm1) {
log(parm1.toString());
}
public void info(Object parm1, Throwable parm2) {
log(parm1.toString() + "\n" + parm2.getStackTrace().toString());
}
public void warn(Object parm1) {
info(parm1);
}
public void warn(Object parm1, Throwable parm2) {
info(parm1, parm2);
}
public void error(Object parm1) {
info(parm1);
}
public void error(Object parm1, Throwable parm2) {
info(parm1, parm2);
}
public void fatal(Object parm1) {
info(parm1);
}
public void fatal(Object parm1, Throwable parm2) {
info(parm1, parm2);
}
} }

272
src/helma/util/Logging.java Normal file
View file

@ -0,0 +1,272 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.util;
import org.apache.commons.logging.*;
import java.io.*;
import java.text.*;
import java.util.*;
/**
* Implementation of Jakarta Commons LogFactory that supports both
* simple console logging and logging to files that are rotated and
* gzipped each night.
*
* @author Stefan Pollack
* @author Daniel Ruthardt
* @author Hannes Wallnoefer
*/
public class Logging extends LogFactory {
// we use one static thread for all Loggers
public static Runner runner;
// the list of active loggers
static ArrayList loggers = new ArrayList();
// hash map of loggers
static HashMap loggerMap = new HashMap();
// log directory
String logdir;
// static console logger
static Logger consoleLog = new Logger(System.out);
/**
* Constructs a log factory, getting the base logging directory from the
* helma.logdir system property.
*/
public Logging() {
logdir = System.getProperty("helma.logdir");
}
/**
* Get a logger for a file name. The log file is created in the
* directory specified by the "log.dir" System property. If the
* logname is "console" a log that writes to System.out is returned.
*/
public Log getInstance(String logname) {
if (logname == null) {
throw new LogConfigurationException("No logname specified!");
}
if ("event".equals(logname))
Thread.dumpStack();
Logger log = null;
if ("console".equals(logdir)) {
log = consoleLog;
} else {
log = (Logger) loggerMap.get(logname);
if (log == null) {
log = newLog(logname);
}
}
if ((runner == null) || !runner.isAlive()) {
runner = new Runner();
runner.start();
}
return log;
}
/**
* Get a logger to System.out.
*/
public static Log getConsoleLog() {
if ((runner == null) || !runner.isAlive()) {
runner = new Runner();
runner.start();
}
return consoleLog;
}
/**
* Add a log to the list of logs and
* create and start the runner thread if necessary.
*/
private synchronized Logger newLog(String logname) {
// check loggerMap again because only now we are synchronized,
// so another thread may have created a log in the meantime.
Logger log = (Logger) loggerMap.get(logname);
if (log != null) {
return log;
}
log = new FileLogger(logdir, logname);
loggerMap.put(logname, log);
loggers.add(log);
return log;
}
public synchronized Log getInstance (Class clazz) {
return getInstance(clazz.toString());
}
public void setAttribute(String name, Object value) {
if ("logdir".equals(name)) {
// FIXME: make log dir changable at runtime
}
}
public Object getAttribute(String name) {
if ("logdir".equals(name)) {
return logdir;
}
return null;
}
public String[] getAttributeNames() {
return new String[] {};
}
public void removeAttribute(String parm1) {
// nothing to do
}
/**
* Flush all logs and shut down.
*/
public void release() {
shutdown();
}
public static void shutdown() {
if (runner != null && runner.isAlive()) {
runner.interrupt();
Thread.yield();
}
closeAll();
}
static void closeAll() {
consoleLog.write();
int nloggers = loggers.size();
for (int i = nloggers - 1; i >= 0; i--) {
FileLogger log = (FileLogger) loggers.get(i);
log.write();
log.closeFile();
}
loggers.clear();
loggerMap.clear();
}
/**
* Rotate log files on all registered logs
*/
static void rotateLogs() {
int nloggers = loggers.size();
for (int i = nloggers - 1; i >= 0; i--) {
FileLogger log = (FileLogger) loggers.get(i);
try {
log.rotateLogFile();
} catch (IOException io) {
System.err.println("Error rotating log " + log.getName() + ": " +
io.toString());
}
}
}
/**
* Returns the timestamp for the next Midnight
*
* @return next midnight timestamp in milliseconds
*/
static long nextMidnight() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, 1 + cal.get(Calendar.DATE));
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 1);
// for testing, rotate the logs every minute:
// cal.set (Calendar.MINUTE, 1 + cal.get(Calendar.MINUTE));
return cal.getTime().getTime();
}
/**
* The static runner class that loops through all loggers.
*/
static class Runner extends Thread {
public synchronized void run() {
long nextMidnight = nextMidnight();
while ((runner == this) && !isInterrupted()) {
long now = System.currentTimeMillis();
if (nextMidnight < now) {
rotateLogs();
nextMidnight = nextMidnight();
}
// write the stdout console log
consoleLog.write();
int nloggers = loggers.size();
for (int i = nloggers-1; i >= 0; i--) {
try {
FileLogger log = (FileLogger) loggers.get(i);
// write out the log entries
log.write();
// if log hasn't been used in the last 30 minutes, close it
if (now - log.lastMessage > 1800000) {
log.closeFile();
}
} catch (Exception x) {
System.err.println("Error in Logger main loop: " + x);
}
}
try {
wait(250);
} catch (InterruptedException ix) {
return;
}
}
}
public synchronized void wakeup() {
notifyAll();
}
}
}