diff --git a/src/schule/ngb/zm/Zeichenmaschine.java b/src/schule/ngb/zm/Zeichenmaschine.java index 090fcfe..83d2eff 100644 --- a/src/schule/ngb/zm/Zeichenmaschine.java +++ b/src/schule/ngb/zm/Zeichenmaschine.java @@ -246,11 +246,13 @@ public class Zeichenmaschine extends Constants { * Aufruf von {@code draw()}. */ public Zeichenmaschine( int width, int height, String title, boolean run_once ) { + LOG.info("Starting " + APP_NAME + " " + APP_VERSION); + // Setzen des "Look&Feel" try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch( Exception e ) { - System.err.println("Error setting the look and feel: " + e.getMessage()); + } catch( Exception ex ) { + LOG.log(Level.SEVERE, "Error setting the look and feel: " + ex.getMessage(), ex); } // Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um @@ -1436,4 +1438,6 @@ public class Zeichenmaschine extends Constants { } + private static final Log LOG = Log.getLogger(Zeichenmaschine.class); + } diff --git a/src/schule/ngb/zm/util/Log.java b/src/schule/ngb/zm/util/Log.java new file mode 100644 index 0000000..dd22f72 --- /dev/null +++ b/src/schule/ngb/zm/util/Log.java @@ -0,0 +1,318 @@ +package schule.ngb.zm.util; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.function.Supplier; +import java.util.logging.*; + +import static java.util.logging.Level.*; + +/** + * Einfache Logging-API, die auf {@link java.util.logging} aufsetzt. + *

+ * Klassen, die Informations- oder Fehlernachrichten loggen wollen, erstellen + * ein internes {@code LOG} Objekt dieser Klasse. Die Zeichenmaschine erstellt + * ihren Logger beispielsweise so: + *

+ *     private static final Log LOG = new Log(Zeichenmaschine.class);
+ * 
+ *

+ * Jedes {@code Log} nutzt intern einen {@link Logger}, der über + * {@link Logger#getLogger(String)} abgerufen wird. Die {@code Log}-Objekte + * selbst werden nicht weiter zwischengespeichert, aber in der Regel wird pro + * Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen + * dann aber denselben {@code Logger}. + *

+ * Die API orientiert sich lose an Log4j und vereinfacht die + * Nutzung der Java logging API für die häufigsten Anwendungsfälle. + */ +public final class Log { + + private static final String ROOT_LOGGER = "schule.ngb.zm"; + + private static boolean LOGGING_INIT = false; + + /** + * Aktiviert das Logging in der Zeichenmaschine global. + *

+ * Die Methode sollte einmalig möglichst früh im Programm aufgerufen werden, + * um für alle bisher und danach erstellten {@link Logger} das minimale + * Logging-Level auf {@link Level#FINE} zu setzen. Dies entspricht allen + * Nachrichten die mit den Methoden (außer {@code trace}) eines {@link Log} + * erzeugt werden. + * + * @see #enableGlobalLevel(Level) + */ + public static final void enableGlobalDebugging() { + enableGlobalLevel(ALL); + } + + /** + * Setzt das Logging-Level aller bisher und danach erzeugten {@link Logger} + * auf den angegebenen {@code Level}. + *

+ * Das Level für bestehende {@code Logger} wird nur abgesenkt, so dass + * Nachrrichten bis {@code level} auf jeden Fall ausgegeben werden. Besitzt + * der {@code Logger} schon ein niedrigeres Level, wird dieses nicht + * verändert. Gleiches gilt für alle {@link ConsoleHandler}, die den + * bestehenden {@code Logger}n hinzugefügt wurden. + *

+ * Hinweis: Das Setzen des Logging-Level während der + * Programmausführung gilt als bad practice, also schlechter + * Programmierstil. Im Kontext der Zeichenmaschine macht dies Sinn, um + * Programmieranfängern eine einfache Möglichkeit zu geben, in ihren + * Programmen auf Fehlersuche zu gehen. Für andere Einsatzszenarien sollte + * auf die übliche Konfiguration des {@link java.util.logging} Pakets über + * eine Konfigurationsdatei zurückgegriffen werden. + * + * @param level Das Level, auf das alle {@code Logger} und {@code Handler} + * mindestens herabgesenkt werden sollen. + */ + public static final void xenableGlobalLevel( Level level ) { + int lvl = Validator.requireNotNull(level).intValue(); + + Logger rootLogger = Logger.getLogger(ROOT_LOGGER); + rootLogger.setLevel(level); + + for( Handler handler : rootLogger.getHandlers() ) { + if( handler instanceof ConsoleHandler ) { + Level handlerLevel = handler.getLevel(); + if( handlerLevel == null || handler.getLevel().intValue() > lvl ) { + handler.setLevel(level); + } + } + } + } + + public static final void enableGlobalLevel( Level level ) { + int lvl = Validator.requireNotNull(level).intValue(); + + // Decrease level of root level ConsoleHandlers for outpu + Logger rootLogger = Logger.getLogger(""); + for( Handler handler : rootLogger.getHandlers() ) { + if( handler instanceof ConsoleHandler ) { + Level handlerLevel = handler.getLevel(); + if( handlerLevel == null || handler.getLevel().intValue() > lvl ) { + handler.setLevel(level); + } + } + } + + // Decrease level of all existing ZM Loggers + Iterator loggerNames = LogManager.getLogManager().getLoggerNames().asIterator(); + while( loggerNames.hasNext() ) { + String loggerName = loggerNames.next(); + + if( loggerName.startsWith(ROOT_LOGGER) ) { + Logger logger = Logger.getLogger(loggerName); + logger.setLevel(level); + + for( Handler handler : logger.getHandlers() ) { + if( handler instanceof ConsoleHandler ) { + Level handlerLevel = handler.getLevel(); + if( handlerLevel == null || handler.getLevel().intValue() > lvl ) { + handler.setLevel(level); + } + } + } + } + } + } + + public static final Log getLogger( Class clazz ) { + if( !LOGGING_INIT ) { + Logger.getLogger(ROOT_LOGGER); + LOGGING_INIT = true; + } + return new Log(clazz); + } + + private final Logger LOGGER; + + private final Class sourceClass; + + private Log( final Class clazz ) { + sourceClass = clazz; + if( !clazz.getName().startsWith(ROOT_LOGGER) ) { + LOGGER = Logger.getLogger(ROOT_LOGGER + "." + clazz.getSimpleName()); + } else { + LOGGER = Logger.getLogger(clazz.getName()); + } + } + + /*public Log( final String name ) { + LOGGER = Logger.getLogger(name); + }*/ + + public void log( Level level, final CharSequence msg ) { + //LOGGER.log(level, msg::toString); + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), msg::toString); + doLog(level, null, msg::toString); + } + } + + public void log( Level level, final CharSequence msg, Object... params ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), () -> String.format(msg.toString(), params)); + doLog(level, null, () -> String.format(msg.toString(), params)); + } + } + + public void log( Level level, final Supplier msgSupplier ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), msgSupplier); + doLog(level, null, msgSupplier); + } + } + + public void log( Level level, final Throwable throwable, final CharSequence msg, Object... params ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), throwable, () -> String.format(msg.toString(), params)); + doLog(level, throwable, () -> String.format(msg.toString(), params)); + } + } + + public void log( Level level, final Throwable throwable, final Supplier msgSupplier ) { + if( LOGGER.isLoggable(level) ) { + //LOGGER.logp(level, sourceClass.getName(), inferCallerName(), throwable, msgSupplier); + doLog(level, throwable, msgSupplier); + } + } + + private String inferCallerName() { + StackTraceElement[] trace = new Throwable().getStackTrace(); + for( int i = 0; i < trace.length; i++ ) { + /// if( trace[i].getClassName().equals(sourceClass.getName()) ) { + if( !trace[i].getClassName().equals(Log.class.getName()) ) { + return trace[i].getMethodName(); + } + } + return "unknown"; + } + + private void doLog( Level level, final Throwable throwable, final Supplier msgSupplier ) { + String clazz = sourceClass.getName(); + String method = "unknown"; + + StackTraceElement[] trace = new Throwable().getStackTrace(); + for( int i = 0; i < trace.length; i++ ) { + if( !trace[i].getClassName().equals(Log.class.getName()) ) { + clazz = trace[i].getClassName(); + method = trace[i].getMethodName(); + break; + } + } + + if( throwable != null ) { + LOGGER.logp(level, clazz, method, throwable, msgSupplier); + } else { + LOGGER.logp(level, clazz, method, msgSupplier); + } + } + + public boolean isLoggable( Level level ) { + return LOGGER.isLoggable(level); + } + + public void info( final CharSequence msg ) { + this.log(INFO, msg); + } + + public void info( final CharSequence msg, Object... params ) { + this.log(INFO, () -> String.format(msg.toString(), params)); + } + + public void info( final Supplier msgSupplier ) { + this.log(INFO, msgSupplier); + } + + public void info( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(INFO, ex, () -> String.format(msg.toString(), params)); + } + + public void info( final Throwable throwable, final Supplier msgSupplier ) { + this.log(INFO, throwable, msgSupplier); + } + + public void warn( final CharSequence msg ) { + this.log(WARNING, msg); + } + + public void warn( final CharSequence msg, Object... params ) { + this.log(WARNING, () -> String.format(msg.toString(), params)); + } + + public void warn( final Supplier msgSupplier ) { + this.log(WARNING, msgSupplier); + } + + public void warn( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(WARNING, ex, () -> String.format(msg.toString(), params)); + } + + public void warn( final Throwable throwable, final Supplier msgSupplier ) { + this.log(WARNING, throwable, msgSupplier); + } + + public void error( final CharSequence msg ) { + this.log(SEVERE, msg); + } + + public void error( final CharSequence msg, Object... params ) { + this.log(SEVERE, () -> String.format(msg.toString(), params)); + } + + public void error( final Supplier msgSupplier ) { + this.log(SEVERE, msgSupplier); + } + + public void error( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(SEVERE, ex, () -> String.format(msg.toString(), params)); + } + + public void error( final Throwable throwable, final Supplier msgSupplier ) { + this.log(SEVERE, throwable, msgSupplier); + } + + public void debug( final CharSequence msg ) { + this.log(FINE, msg); + } + + public void debug( final CharSequence msg, Object... params ) { + this.log(FINE, () -> String.format(msg.toString(), params)); + } + + public void debug( final Supplier msgSupplier ) { + this.log(FINE, msgSupplier); + } + + public void debug( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(FINE, ex, () -> String.format(msg.toString(), params)); + } + + public void debug( final Throwable throwable, final Supplier msgSupplier ) { + this.log(FINE, throwable, msgSupplier); + } + + public void trace( final CharSequence msg ) { + this.log(FINER, msg); + } + + public void trace( final CharSequence msg, Object... params ) { + this.log(FINER, () -> String.format(msg.toString(), params)); + } + + public void trace( final Supplier msgSupplier ) { + this.log(FINER, msgSupplier); + } + + public void trace( final Throwable ex, final CharSequence msg, Object... params ) { + this.log(FINER, ex, () -> String.format(msg.toString(), params)); + } + + public void trace( final Throwable throwable, final Supplier msgSupplier ) { + this.log(FINER, throwable, msgSupplier); + } + +}