org.audiveris.omr.log.LogUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.audiveris.omr.log.LogUtil.java

Source

//------------------------------------------------------------------------------------------------//
//                                                                                                //
//                                          L o g U t i l                                         //
//                                                                                                //
//------------------------------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">
//
//  Copyright  Audiveris 2018. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify it under the terms of the
//  GNU Affero General Public License as published by the Free Software Foundation, either version
//  3 of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
//  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//  See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with this
//  program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------------------------//
// </editor-fold>
package org.audiveris.omr.log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.util.StatusPrinter;

import org.apache.commons.io.FileUtils;

import org.audiveris.omr.WellKnowns;
import org.audiveris.omr.sheet.Book;
import org.audiveris.omr.sheet.SheetStub;
import org.audiveris.omr.util.UriUtil;

import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.swing.SwingUtilities;

/**
 * Class {@code LogUtil} handles logging features based on underlying LogBack binding.
 *
 * @author Herv Bitteur
 */
public abstract class LogUtil {

    /** MDC key for book context. */
    public static final String BOOK = "BOOK";

    /** MDC key for sheet/stub context. */
    public static final String SHEET = "SHEET";

    /** System property for LogBack configuration. */
    private static final String LOGBACK_LOGGING_KEY = "logback.configurationFile";

    /** File name for LogBack configuration. */
    private static final String LOGBACK_FILE_NAME = "logback.xml";

    /** Initial messages before logging is fully set. */
    private static final List<String> initialMessages = new ArrayList<>();

    //-------------//
    // addAppender //
    //-------------//
    /**
     * Start a specific file logging, typically for the processing of a given book.
     *
     * @param name      appender name (typically the book radix)
     * @param logFolder target folder where the log file is to be written
     */
    public static void addAppender(String name, Path logFolder) {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        FileAppender fileAppender = new FileAppender();
        PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder();
        fileAppender.setName(name);
        fileAppender.setContext(loggerContext);
        fileAppender.setAppend(false);

        String now = new SimpleDateFormat("yyyyMMdd'T'HHmm").format(new Date());
        Path logFile = logFolder.resolve(name + "-" + now + ".log");
        fileAppender.setFile(logFile.toAbsolutePath().toString());
        fileEncoder.setContext(loggerContext);
        fileEncoder.setPattern("%date %level [%X{BOOK}%X{SHEET}] %25file:%-4line | %msg%n%ex");
        fileEncoder.start();
        fileAppender.setEncoder(fileEncoder);
        fileAppender.start();
        root.addAppender(fileAppender);
    }

    //-----------------//
    // addFileAppender //
    //-----------------//
    /**
     * Add a specific appender meant for FILE.
     */
    public static void addFileAppender() {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        FileAppender fileAppender = new FileAppender();
        PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder();
        fileAppender.setName("FILE");
        fileAppender.setContext(loggerContext);
        fileAppender.setAppend(false);

        String now = new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(new Date());
        Path logPath = WellKnowns.LOG_FOLDER.resolve(now + ".log").toAbsolutePath();
        fileAppender.setFile(logPath.toString());
        fileEncoder.setContext(loggerContext);
        fileEncoder.setPattern(
                "%date %-5level [%X{BOOK}%X{SHEET}] %25replace(%file){'\\.java$',''} %-4line | %msg%n%ex");
        fileEncoder.start();
        fileAppender.setEncoder(fileEncoder);
        fileAppender.start();
        root.addAppender(fileAppender);

        initMessage("LogUtil. Logging to " + logPath);
    }

    //----------------//
    // addGuiAppender //
    //----------------//
    /**
     * Add a specific appender meant for GUI log pane.
     * To be called only when running with a GUI.
     */
    public static void addGuiAppender() {
        // GUI (filtered in LogGuiAppender)
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        Appender guiAppender = new LogGuiAppender();
        guiAppender.setName("GUI");
        guiAppender.setContext(loggerContext);
        guiAppender.start();
        root.addAppender(guiAppender);
    }

    //--------------------//
    // allInitialMessages //
    //--------------------//
    /**
     * Report messages recorded before logging was fully set.
     *
     * @return initial messages concatenated into one string
     */
    public static String allInitialMessages() {
        StringBuilder sb = new StringBuilder();

        for (String str : initialMessages) {
            sb.append(str).append("\n");
        }

        return sb.toString();
    }

    //------------//
    // initialize //
    //------------//
    /**
     * Check for (BackLog) logging configuration, and if not found,
     * define a minimal configuration.
     * This method should be called at the very beginning of the program before
     * any logging request is sent.
     *
     * @param CONFIG_FOLDER Config folder which may contain a logback.xml file
     * @param RES_URI       uri to 'res' folder (within .jar or development hierarchy)
     */
    public static void initialize(Path CONFIG_FOLDER, URI RES_URI) {
        // 1/ Check if system property is set and points to a real file
        final String loggingProp = System.getProperty(LOGBACK_LOGGING_KEY);

        if (loggingProp != null) {
            Path configPath = Paths.get(loggingProp).toAbsolutePath();

            if (Files.exists(configPath)) {
                // Everything seems OK, let LogBack use the config file
                initMessage("LogUtil. Configuration found " + configPath);

                return;
            } else {
                initMessage("LogUtil. File " + configPath + " does not exist.");
            }
        } else {
            initMessage("LogUtil. Property " + LOGBACK_LOGGING_KEY + " not defined, skipped.");
        }

        // 2/ Look for well-known location (user Audiveris config folder)
        Path configPath = CONFIG_FOLDER.resolve(LOGBACK_FILE_NAME).toAbsolutePath();

        if (Files.exists(configPath)) {
            initMessage("LogUtil. Configuration found " + configPath);
            System.setProperty(LOGBACK_LOGGING_KEY, configPath.toString());

            return;
        } else {
            initMessage("LogUtil. No " + configPath + ", skipped.");
        }

        // 3/ Look for suitable file within 'res' folder or resource
        try {
            final URI configUri = UriUtil.toURI(RES_URI, LOGBACK_FILE_NAME);
            final Path localPath;

            if (configUri.toString().startsWith("jar:")) {
                // Make a temporary copy off .jar archive
                File tmpFile = File.createTempFile("logback-", ".xml");
                tmpFile.deleteOnExit();

                try (InputStream is = configUri.toURL().openStream()) {
                    FileUtils.copyInputStreamToFile(is, tmpFile);
                }

                localPath = tmpFile.toPath();
            } else {
                localPath = Paths.get(configUri);
            }

            if (Files.exists(localPath)) {
                initMessage("LogUtil. Configuration found " + configUri);
                System.setProperty(LOGBACK_LOGGING_KEY, localPath.toString());

                return;
            } else {
                initMessage("LogUtil. No " + localPath + ", skipped.");
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // 4/ We need a default configuration
        initMessage("LogUtil. Building a minimal Logging configuration");

        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

        // CONSOLE
        ConsoleAppender consoleAppender = new ConsoleAppender();
        PatternLayoutEncoder consoleEncoder = new PatternLayoutEncoder();
        consoleAppender.setName("CONSOLE");
        consoleAppender.setContext(loggerContext);
        consoleEncoder.setContext(loggerContext);
        consoleEncoder.setPattern("%-5level %caller{1} [%X{BOOK}%X{SHEET}] %msg%n%ex");
        consoleEncoder.start();
        consoleAppender.setEncoder(consoleEncoder);
        consoleAppender.start();
        root.addAppender(consoleAppender);

        // Levels
        root.setLevel(Level.INFO);

        // OPTIONAL: print logback internal status messages
        StatusPrinter.print(loggerContext);
    }

    //----------------//
    // removeAppender //
    //----------------//
    /**
     * Terminate the specific file logging.
     *
     * @param name appender name (typically the book radix)
     */
    public static void removeAppender(String name) {
        Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        root.detachAppender(name);
    }

    //-------//
    // start //
    //-------//
    /**
     * In the calling thread, start log annotation with stub ID.
     *
     * @param stub the sheet/stub related to processing
     */
    public static void start(SheetStub stub) {
        start(stub.getBook());

        if (!SwingUtilities.isEventDispatchThread()) {
            MDC.put(SHEET, stub.getNum());
        }
    }

    //-------//
    // start //
    //-------//
    /**
     * In the calling thread, start log annotation with book ID.
     *
     * @param book the book related to processing
     */
    public static void start(Book book) {
        if (!SwingUtilities.isEventDispatchThread()) {
            String str = book.getAlias();

            if (str == null) {
                str = book.getRadix();
            }

            MDC.put(BOOK, str);
        }
    }

    //----------//
    // stopBook //
    //----------//
    /**
     * In the calling thread, stop book log annotation.
     */
    public static void stopBook() {
        stopStub();

        if (!SwingUtilities.isEventDispatchThread()) {
            MDC.remove(BOOK);
        }
    }

    //----------//
    // stopStub //
    //----------//
    /**
     * In the calling thread, stop sheet stub log annotation.
     */
    public static void stopStub() {
        if (!SwingUtilities.isEventDispatchThread()) {
            MDC.remove(SHEET);
        }
    }

    //---------//
    // toLevel //
    //---------//
    /**
     * Decode a string as a Level value.
     *
     * @param str the input string
     * @return the decoded Level value
     */
    public static Level toLevel(final String str) {
        switch (str.toUpperCase()) {
        case "ALL":
            return Level.ALL;

        case "TRACE":
            return Level.TRACE;

        case "DEBUG":
            return Level.DEBUG;

        case "INFO":
            return Level.INFO;

        case "WARN":
            return Level.WARN;

        case "ERROR":
            return Level.ERROR;

        default:
        case "OFF":
            return Level.OFF;
        }
    }

    //-------------//
    // initMessage //
    //-------------//
    /**
     * Record a message before logging is fully set.
     *
     * @param str the message to record
     */
    private static void initMessage(String str) {
        initialMessages.add(str);
    }

    private LogUtil() {
    }
}