From 0448ae3c8aa0e846a42ebaecbc6ed4638c813a5f Mon Sep 17 00:00:00 2001 From: Tom Bentley Date: Wed, 22 Aug 2012 09:07:16 +0100 Subject: [PATCH] A stab at a logging API (see #4605) Issues with this: * No tests yet, since it's for discussion. * Being able to change the threshold level of the DefaultLog is all very well but that only works if the default log is the one being used. OTOH, we need to support things like `ceylon compile --verbose`. * The same points as are made in #4606 also apply to logging., so having a single place (LogFactory.setFactory()) where 'the' LogFactory defined for a whole application isn't ideal. --- .../redhat/ceylon/common/log/DefaultLog.java | 86 +++++++++++++++++++ .../src/com/redhat/ceylon/common/log/Log.java | 50 +++++++++++ .../redhat/ceylon/common/log/LogFactory.java | 86 +++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 common/src/com/redhat/ceylon/common/log/DefaultLog.java create mode 100644 common/src/com/redhat/ceylon/common/log/Log.java create mode 100644 common/src/com/redhat/ceylon/common/log/LogFactory.java diff --git a/common/src/com/redhat/ceylon/common/log/DefaultLog.java b/common/src/com/redhat/ceylon/common/log/DefaultLog.java new file mode 100644 index 00000000000..f65d4f4c558 --- /dev/null +++ b/common/src/com/redhat/ceylon/common/log/DefaultLog.java @@ -0,0 +1,86 @@ +package com.redhat.ceylon.common.log; + +import java.io.PrintStream; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A default implementation of {@link Log} which prints to standard error. + * This is probably suitable for most command line applications. + * Other applications should set a {@link LogFactory} so that they can do + * something suitable. + * @author tom + */ +public class DefaultLog implements Log { + + private static enum Level { + OFF, + ERROR, + WARN, + INFO, + DEBUG + } + + private static AtomicReference LEVEL = new AtomicReference(Level.WARN); + + public static void setLevel(Level level) { + DefaultLog.LEVEL.set(level); + } + + private final PrintStream out = System.err; + + private DefaultLog() { + } + + protected boolean isEnabled(Level level) { + return level.ordinal() <= this.LEVEL.get().ordinal(); + } + + @Override + public void error(String message, Throwable cause) { + if (isEnabled(Level.ERROR)) { + out.println(message); + if (cause != null) { + cause.printStackTrace(out); + } + } + } + + @Override + public void warning(String message, Throwable cause) { + if (isEnabled(Level.WARN)) { + out.println(message); + if (cause != null) { + cause.printStackTrace(out); + } + } + } + + @Override + public void info(String message, Throwable cause) { + if (isEnabled(Level.INFO)) { + out.println(message); + if (cause != null) { + cause.printStackTrace(out); + } + } + } + + @Override + public void debug(String message, Throwable cause) { + if (isEnabled(Level.DEBUG)) { + out.println(message); + if (cause != null) { + cause.printStackTrace(out); + } + } + } + + private static DefaultLog instance = null; + static synchronized DefaultLog getInstance() { + if (instance == null) { + instance = new DefaultLog(); + } + return instance; + } + +} diff --git a/common/src/com/redhat/ceylon/common/log/Log.java b/common/src/com/redhat/ceylon/common/log/Log.java new file mode 100644 index 00000000000..63a38028895 --- /dev/null +++ b/common/src/com/redhat/ceylon/common/log/Log.java @@ -0,0 +1,50 @@ +package com.redhat.ceylon.common.log; + +/** + *

A very simple log facade.

+ * + *

At the point of use a {@code Log} instance is created like this:

+ *
+ * Log log = LogFactory.getLog("foo");
+ * // or
+ * log = LogFactory.getLog(Foo.class);
+ * 
+ * + * @see LogFactory + * @author tom + */ +public interface Log { + + /** + * Log an error. + * Do this if something is definitely wrong, but the program can continue. + * If the program cannot continue, you should throw an exception. + */ + public void error(String str, Throwable cause); + + /** + * Log a warning. + * Do this if something is probably wrong, but the program can continue. + * If something is definitely wrong, call + * {@link #error(String, Throwable)} instead. + */ + public void warning(String str, Throwable cause); + + /** + * Log some information. + * Do this if nothing is wrong, but the user might be interested. + * If something is probably wrong call + * {@link #warning(String, Throwable)} instead. + */ + public void info(String str, Throwable cause); + + /** + * Log some debugging information. + * Do this if nothing is wrong, but the message could be useful to + * someone debugging a problem. + * If the user might be interested call + * {@link #info(String, Throwable)} instead. + */ + public void debug(String str, Throwable cause); + +} diff --git a/common/src/com/redhat/ceylon/common/log/LogFactory.java b/common/src/com/redhat/ceylon/common/log/LogFactory.java new file mode 100644 index 00000000000..8864b97bcd8 --- /dev/null +++ b/common/src/com/redhat/ceylon/common/log/LogFactory.java @@ -0,0 +1,86 @@ +package com.redhat.ceylon.common.log; + +import com.redhat.ceylon.common.config.CeylonConfig; + +/** + *

The factory used to create {@link Log} instances.

+ * + *

The factory methods used to create {@link Log} instances + * are {@link #getLog(String)} and {@link #getLog(Class)}. It is implementation + * dependent whether these return new instances each time they are called with + * the same argument.

+ * + *

Configuring a LogFactory

+ * + *
    + *
  1. If {@link #setFactory(LogFactory)} has been called then that instance + * is used.
  2. + *
  3. Otherwise, if the {@code logging.factory} property of the default + * {@linkplain CeylonConfig configuration} is not null it is assumed to be + * the name of a subclass of {@code LogFactory}, and an instance is + * created using the nullary constructor. If an instance is successfully + * created it is used as the {@code LogFactory}.
  4. + *
  5. Otherwise (if the {@code logging.factory} property was null, or an + * instance could not be created), an instance of the LogFactory itself is + * used as the {@code LogFactory}.
  6. + *
  7. If instantiation of the given {@code logging.factory} was attempted + * and failed, a {@linkplain Log#error(String, Throwable) error} is + * logged
  8. + *
+ * + *

Note: An application seeking to replace the default {@code LogFactory} with its + * own implementation by calling {@link #setFactory(LogFactory)}, but it must + * do so before the {@code Log} factory methods are called, (i.e. before any log + * messages are generated).

+ * + * @author tom + */ +public class LogFactory { + + protected LogFactory() { + } + + private static LogFactory logFactory = null; + + private static synchronized LogFactory getFactory() { + if (logFactory == null) { + String factoryClassName = CeylonConfig.get("logging.factory"); + if (factoryClassName != null) { + try { + logFactory = (LogFactory) Class.forName(factoryClassName).newInstance(); + } catch (Exception e) { + DefaultLog.getInstance().error("Error initializing logging factory", e); + } + } + if (logFactory == null) { + logFactory = new LogFactory(); + } + } + return logFactory; + } + + /** + * Sets the factory to use to create {@link Log} instances. This must be + * before any Log instances are created. + * @param factory + */ + public static synchronized void setFactory(LogFactory factory) { + if (logFactory != null) { + throw new RuntimeException("Too late!"); + } + logFactory = factory; + } + + protected Log createLog(String category) { + return DefaultLog.getInstance(); + } + + public static Log getLog(String category) { + return getFactory().createLog(category); + } + + public static Log getLog(Class cls) { + return getLog(cls.getName()); + } + +}