ilscipio/scipio-erp

View on GitHub
framework/base/src/org/ofbiz/base/util/Debug.java

Summary

Maintainability
F
4 days
Test Coverage
/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *******************************************************************************/
package org.ofbiz.base.util;

import java.util.*;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Configurable Debug logging wrapper class
 *
 */
public final class Debug {

    private static final String noModuleModule = "NoModule";  // set to null for previous behavior
    private static final Object[] emptyParams = new Object[0];

    public static final int ALWAYS = 0;
    public static final int VERBOSE = 1;
    public static final int TIMING = 2;
    public static final int INFO = 3;
    public static final int IMPORTANT = 4;
    public static final int WARNING = 5;
    public static final int ERROR = 6;
    public static final int FATAL = 7;

    // SCIPIO: 3.0.0: Added/standardized helpful constants (renamed from levelStringMap)
    // Levels: always, verbose, timing, info, important, warning", error, fatal
    public static final Set<String> LEVEL_NAMES = Set.of("always", "verbose", "timing", "info", "important", "warning", "error", "fatal");
    public static final List<String> LEVEL_NAMES_LIST = List.copyOf(LEVEL_NAMES);
    public static final Map<String, Integer> LEVEL_NAMES_MAP = Map.of(
            "always", Debug.ALWAYS,
            "verbose", Debug.VERBOSE,
            "timing", Debug.TIMING,
            "info", Debug.INFO,
            "important", Debug.IMPORTANT,
            "warning", Debug.WARNING,
            "error", Debug.ERROR,
            "fatal", Debug.FATAL);

    /**
     * SCIPIO: The locale of the log entries. This is always {@link Locale#ENGLISH}.
     */
    private static final Locale LOG_LOCALE = Locale.ENGLISH;

    private static final String[] levelProps = {"", "print.verbose", "print.timing", "print.info", "print.important", "print.warning", "print.error", "print.fatal"};
    private static final Level[] levelObjs = {Level.OFF, Level.DEBUG, Level.TRACE, Level.INFO, Level.INFO, Level.WARN, Level.ERROR, Level.FATAL};

    private static final boolean levelOnCache[] = new boolean[8]; // this field is not thread safe

    /**
     * SCIPIO: Thread-local support to disable levels like warning & error temporarily
     * - for internal framework use only - do not use. (2018-10-12)
     * 2018-10-12: Only works for warning & error; ThreadLocal too expensive for info & lower.
     */
    private static final ThreadLocal<boolean[]> threadLevelAllowCache = new ThreadLocal<boolean[]>() {
        @Override
        protected boolean[] initialValue() {
            return levelAllowAll;
        }
    };
    private static final boolean[] levelsWithThreadSupport = new boolean[] {
            false, false, false, false, false, true, true, false
    };
    private static final boolean[] levelAllowAll = new boolean[] {
            true, true, true, true, true, true, true, true
    };
    private static final boolean[] levelAllowNoError = new boolean[] {
            true, true, true, true, true, true, false, true
    };
    private static final boolean[] levelAllowNoWarningError = new boolean[] {
            true, true, true, true, true, false, false, true
    } ;

    private static final Logger root = LogManager.getRootLogger();

    private static final Debug INSTANCE = new Debug(); // SCIPIO: This is for FreeMarkerWorker (only!)
    
    static {

        // initialize levelOnCache
        Properties properties = UtilProperties.createProperties("debug.properties");
        if (properties != null) {
            for (int i = 0; i < levelOnCache.length; i++) {
                levelOnCache[i] = (i == Debug.ALWAYS || "true".equalsIgnoreCase(properties.getProperty(levelProps[i])));
            }
        } else {
            throw new IllegalStateException("debug.properties file not found");
        }
    }

    /**
     * SCIPIO: A workaround for .groovy and .ftl module names not showing.
     * FIXME?: This should maybe solved another way as this might impact performance?
     * Added 2016-11-11 (modified 2019-01-31).
     */
    private static String checkStripModuleExt(String module) {
        if (module.endsWith(".groovy")) {
            return module.substring(0, module.length() - ".groovy".length());
        } else if (module.endsWith(".ftl")) {
            return module.substring(0, module.length() - ".ftl".length());
        }
        return module;
    }

    public static Logger getLogger(String module) {
        // SCIPIO: refactored for checkStripModuleExt
        if (module != null && !module.isEmpty()) {
            return LogManager.getLogger(checkStripModuleExt(module));
        } else {
            return root;
        }
    }

    /** Gets an Integer representing the level number from a String representing the level name; will return null if not found. */
    public static Integer getLevelFromString(String levelName) {
        if (levelName == null) {
            return null;
        }
        return LEVEL_NAMES_MAP.get(levelName.toLowerCase(Locale.getDefault()));
    }

    /** Gets an Integer representing the level number from a String representing the level name; will return default value if not found (SCIPIO). */
    public static Integer getLevelFromString(String levelName, Integer defaultLevel) {
        Integer level = getLevelFromString(levelName);
        return (level != null) ? level : defaultLevel;
    }

    /** Gets an Integer representing the level number from a Number or String representing the level name; will return null if not found (SCIPIO). */
    public static Integer getLevel(Object levelName) {
        if (levelName instanceof Number) {
            return ((Number) levelName).intValue();
        } else {
            return getLevelFromString((String) levelName);
        }
    }

    /** Gets an Integer representing the level number from a Number or String representing the level name; will return default value if not found (SCIPIO). */
    public static Integer getLevel(Object levelName, Integer defaultLevel) {
        Integer level = getLevel(levelName);
        return (level != null) ? level : defaultLevel;
    }

    public static void log(int level, Throwable t, String msg, String module) {
        log(level, t, msg, module, "org.ofbiz.base.util.Debug", emptyParams);
    }

    public static void log(int level, Throwable t, String msg, String module, Object... params) {
        log(level, t, msg, module, "org.ofbiz.base.util.Debug", params);
    }

    public static void log(int level, Throwable t, String msg, String module, String callingClass) {
        log(level, t, msg, module, callingClass, new Object[0]);
    }

    public static void log(int level, Throwable t, String msg, String module, String callingClass, Object... params) {
        if (isOn(level) && isOnForThread(level)) { // SCIPIO: 2018-10-12: isOnForThread
            if (msg != null && params.length > 0) {
                StringBuilder sb = new StringBuilder();
                Formatter formatter = new Formatter(sb);
                formatter.format(msg, params);
                msg = sb.toString();
                formatter.close();
            }

            // log
            Logger logger = getLogger(module);
            logger.log(levelObjs[level], msg, t);
        }
    }

    public static boolean isOn(int level) {
        return levelOnCache[level];
    }

    public static boolean isLowerEqualPrio(Object testLevel, int maxLevel) { // SCIPIO
        Integer level = getLevel(testLevel);
        return (level != null && level != TIMING) && (level <= maxLevel);
    }

    public static boolean isHigherEqualPrio(Object testLevel, int minLevel) { // SCIPIO
        Integer level = getLevel(testLevel);
        return (level != null && level != TIMING) && (level >= minLevel || level == ALWAYS);
    }

    // leaving these here
    public static void log(String msg) {
        log(Debug.ALWAYS, null, msg, noModuleModule, emptyParams);
    }

    public static void log(String msg, Object... params) {
        log(Debug.ALWAYS, null, msg, noModuleModule, params);
    }

    public static void log(Throwable t) {
        log(Debug.ALWAYS, t, null, noModuleModule, emptyParams);
    }

    public static void log(String msg, String module) {
        log(Debug.ALWAYS, null, msg, module, emptyParams);
    }

    public static void log(String msg, String module, Object... params) {
        log(Debug.ALWAYS, null, msg, module, params);
    }

    public static void log(Throwable t, String module) {
        log(Debug.ALWAYS, t, null, module, emptyParams);
    }

    public static void log(Throwable t, String msg, String module) {
        log(Debug.ALWAYS, t, msg, module, emptyParams);
    }

    public static void log(Throwable t, String msg, String module, Object... params) {
        log(Debug.ALWAYS, t, msg, module, params);
    }

    public static boolean verboseOn() {
        return isOn(Debug.VERBOSE);
    }

    public static void logVerbose(String msg, String module) {
        log(Debug.VERBOSE, null, msg, module, emptyParams);
    }

    public static void logVerbose(String msg, String module, Object... params) {
        log(Debug.VERBOSE, null, msg, module, params);
    }

    public static void logVerbose(Throwable t, String module) {
        log(Debug.VERBOSE, t, null, module, emptyParams);
    }

    public static void logVerbose(Throwable t, String msg, String module) {
        log(Debug.VERBOSE, t, msg, module, emptyParams);
    }

    public static void logVerbose(Throwable t, String msg, String module, Object... params) {
        log(Debug.VERBOSE, t, msg, module, params);
    }

    public static boolean timingOn() {
        return isOn(Debug.TIMING);
    }

    public static void logTiming(String msg, String module) {
        log(Debug.TIMING, null, msg, module, emptyParams);
    }

    public static void logTiming(String msg, String module, Object... params) {
        log(Debug.TIMING, null, msg, module, params);
    }

    public static void logTiming(Throwable t, String module) {
        log(Debug.TIMING, t, null, module, emptyParams);
    }

    public static void logTiming(Throwable t, String msg, String module) {
        log(Debug.TIMING, t, msg, module, emptyParams);
    }

    public static void logTiming(Throwable t, String msg, String module, Object... params) {
        log(Debug.TIMING, t, msg, module, params);
    }

    public static boolean infoOn() {
        return isOn(Debug.INFO);
    }

    public static void logInfo(String msg, String module) {
        log(Debug.INFO, null, msg, module, emptyParams);
    }

    public static void logInfo(String msg, String module, Object... params) {
        log(Debug.INFO, null, msg, module, params);
    }

    public static void logInfo(Throwable t, String module) {
        log(Debug.INFO, t, null, module, emptyParams);
    }

    public static void logInfo(Throwable t, String msg, String module) {
        log(Debug.INFO, t, msg, module, emptyParams);
    }

    public static void logInfo(Throwable t, String msg, String module, Object... params) {
        log(Debug.INFO, t, msg, module, params);
    }

    public static boolean importantOn() {
        return isOn(Debug.IMPORTANT);
    }

    public static void logImportant(String msg, String module) {
        log(Debug.IMPORTANT, null, msg, module, emptyParams);
    }

    public static void logImportant(String msg, String module, Object... params) {
        log(Debug.IMPORTANT, null, msg, module, params);
    }

    public static void logImportant(Throwable t, String module) {
        log(Debug.IMPORTANT, t, null, module, emptyParams);
    }

    public static void logImportant(Throwable t, String msg, String module) {
        log(Debug.IMPORTANT, t, msg, module, emptyParams);
    }

    public static void logImportant(Throwable t, String msg, String module, Object... params) {
        log(Debug.IMPORTANT, t, msg, module, params);
    }

    public static boolean warningOn() {
        return isOn(Debug.WARNING);
    }

    public static void logWarning(String msg, String module) {
        log(Debug.WARNING, null, msg, module, emptyParams);
    }

    public static void logWarning(String msg, String module, Object... params) {
        log(Debug.WARNING, null, msg, module, params);
    }

    public static void logWarning(Throwable t, String module) {
        log(Debug.WARNING, t, null, module, emptyParams);
    }

    public static void logWarning(Throwable t, String msg, String module) {
        log(Debug.WARNING, t, msg, module, emptyParams);
    }

    public static void logWarning(Throwable t, String msg, String module, Object... params) {
        log(Debug.WARNING, t, msg, module, params);
    }

    public static boolean errorOn() {
        return isOn(Debug.ERROR);
    }

    public static void logError(String msg, String module) {
        log(Debug.ERROR, null, msg, module, emptyParams);
    }

    public static void logError(String msg, String module, Object... params) {
        log(Debug.ERROR, null, msg, module, params);
    }

    public static void logError(Throwable t, String module) {
        log(Debug.ERROR, t, null, module, emptyParams);
    }

    public static void logError(Throwable t, String msg, String module) {
        log(Debug.ERROR, t, msg, module, emptyParams);
    }

    public static void logError(Throwable t, String msg, String module, Object... params) {
        log(Debug.ERROR, t, msg, module, params);
    }

    public static boolean fatalOn() {
        return isOn(Debug.FATAL);
    }

    public static void logFatal(String msg, String module) {
        log(Debug.FATAL, null, msg, module, emptyParams);
    }

    public static void logFatal(String msg, String module, Object... params) {
        log(Debug.FATAL, null, msg, module, params);
    }

    public static void logFatal(Throwable t, String module) {
        log(Debug.FATAL, t, null, module, emptyParams);
    }

    public static void logFatal(Throwable t, String msg, String module) {
        log(Debug.FATAL, t, msg, module, emptyParams);
    }

    public static void logFatal(Throwable t, String msg, String module, Object... params) {
        log(Debug.FATAL, t, msg, module, params);
    }

    public static void set(int level, boolean on) {
        levelOnCache[level] = on;
    }

    public static boolean get(int level) {
        return levelOnCache[level];
    }

    /**
     * SCIPIO: Optimized object-based wrapper around the Debug logging methods.
     * <p>
     * 2018-05-24: This should now be used in all Java classes instead of the old module String:
     * <pre>{@code
     *     private static final Debug.OfbizLogger module = Debug.getOfbizLogger(java.lang.invoke.MethodHandles.lookup().lookupClass());
     * }</pre>
     * This should be used in Groovy scripts:
     * <pre>{@code
     *     final module = Debug.getOfbizLogger(getClass());
     * }</pre>
     * <p>
     * In method bodies, the same <code>Debug.logXxx</code> invocation format as before can be preserved since new overloads
     * are added which accept a OfbizLogger object.
     * <p>
     * <strong>NOTE:</strong> 2018-05-24: Although the logXxx methods on this logger can be used,
     * for consistency reasons it is recommended to use the Debug.logXxx overloads which accept a OfbizLogger
     * as parameter, for the foreseeable future. There is no real performance difference.
     * <p>
     * When used in this fashion, this wrapper object provides optimized caching of the log4j
     * {@link org.apache.logging.log4j.Logger} instance for much faster <code>Debug.logXxx</code> invocations,
     * compared to the legacy invocations that take a module String, which incur some string and lookup overhead.
     * <p>
     * Added 2018-05-24.
     */
    public static class OfbizLogger {
        private static final OfbizLogger rootOfbizLogger = OfbizLogger.getInstance(Debug.root);

        private final Logger log4jLogger;

        private OfbizLogger(Logger log4jLogger) {
            this.log4jLogger = log4jLogger;
        }

        /**
         * Returns a new OfbizLogger wrapper logger around the given log4j
         * {@link org.apache.logging.log4j.Logger} instance.
         */
        public static OfbizLogger getInstance(Logger log4jLogger) {
            return new OfbizLogger(log4jLogger);
        }

        /**
         * Returns a new OfbizLogger wrapper logger for the given Ofbiz
         * Java class.
         * <p>
         * For Java, the class can be gotten by invoking
         * <code>java.lang.invoke.MethodHandles.lookup().lookupClass()</code>
         * in the calling class.
         */
        public static OfbizLogger getInstance(Class<?> cls) {
            return (cls != null) ? new OfbizLogger(LogManager.getLogger(cls)) : rootOfbizLogger;
        }

        /**
         * Returns a new OfbizLogger wrapper logger for the given Ofbiz
         * Java or Groovy class module name.
         * <p>
         * For Java, the module name can be gotten by invoking
         * <code>java.lang.invoke.MethodHandles.lookup().lookupClass()</code>
         * in the calling class.
         * <p>
         * For Groovy, the module name can simply be specified as "MyScriptName.groovy".
         */
        public static OfbizLogger getInstance(String module) {
            return UtilValidate.isNotEmpty(module) ? new OfbizLogger(Debug.getLogger(module)) : rootOfbizLogger;
        }

        /**
         * Returns the root OfbizLogger logger wrapper.
         */
        public static OfbizLogger getInstance() {
            return rootOfbizLogger;
        }

        public Logger getLogger() {
            return log4jLogger;
        }

        /**
         * Returns the logger name, in other words the module string.
         * For Java classes, this is the fully-qualified class name.
         */
        public String getName() {
            return log4jLogger.getName();
        }

        @Override
        public String toString() {
            // Return the logger name, for backward compat, for anywhere module string
            // was used in string concatenations
            return log4jLogger.getName();
        }

        @Override
        public boolean equals(Object obj) {
            // For backward compat, workaround for module.equals(...) invocations
            if (obj == null) return false;
            return toString().equals(obj.toString());
        }

        @Override
        public int hashCode() {
            return log4jLogger.getName().hashCode();
        }

        public void log(int level, String msg) {
            log(level, null, msg, "org.ofbiz.base.util.Debug$OfbizLogger", emptyParams);
        }

        public void log(int level, Throwable t, String msg) {
            log(level, t, msg, "org.ofbiz.base.util.Debug$OfbizLogger", emptyParams);
        }

        public void log(int level, Throwable t, String msg, Object... params) {
            log(level, t, msg, "org.ofbiz.base.util.Debug$OfbizLogger", params);
        }

        public void log(int level, Throwable t, String msg, String callingClass) {
            log(level, t, msg, callingClass, new Object[0]);
        }

        public void log(int level, Throwable t, String msg, String callingClass, Object... params) {
            if (isOn(level)) {
                if (msg != null && params.length > 0) {
                    StringBuilder sb = new StringBuilder();
                    Formatter formatter = new Formatter(sb);
                    formatter.format(msg, params);
                    msg = sb.toString();
                    formatter.close();
                }

                // log
                log4jLogger.log(levelObjs[level], msg, t);
            }
        }

        public boolean isOn(int level) {
            return levelOnCache[level];
        }

        public void log(String msg) {
            log(Debug.ALWAYS, null, msg, emptyParams);
        }

        public void log(String msg, Object... params) {
            log(Debug.ALWAYS, null, msg, params);
        }

        public void log(Throwable t) {
            log(Debug.ALWAYS, t, null, emptyParams);
        }

        public void log(Throwable t, String msg) {
            log(Debug.ALWAYS, t, msg, emptyParams);
        }

        public void log(Throwable t, String msg, Object... params) {
            log(Debug.ALWAYS, t, msg, params);
        }

        public boolean verboseOn() {
            return isOn(Debug.VERBOSE);
        }

        public void logVerbose(String msg) {
            log(Debug.VERBOSE, null, msg, emptyParams);
        }

        public void logVerbose(String msg, Object... params) {
            log(Debug.VERBOSE, null, msg, params);
        }

        public void logVerbose(Throwable t) {
            log(Debug.VERBOSE, t, null, emptyParams);
        }

        public void logVerbose(Throwable t, String msg) {
            log(Debug.VERBOSE, t, msg, emptyParams);
        }

        public void logVerbose(Throwable t, String msg, Object... params) {
            log(Debug.VERBOSE, t, msg, params);
        }

        public boolean timingOn() {
            return isOn(Debug.TIMING);
        }

        public void logTiming(String msg) {
            log(Debug.TIMING, null, msg, emptyParams);
        }

        public void logTiming(String msg, Object... params) {
            log(Debug.TIMING, null, msg, params);
        }

        public void logTiming(Throwable t) {
            log(Debug.TIMING, t, null, emptyParams);
        }

        public void logTiming(Throwable t, String msg) {
            log(Debug.TIMING, t, msg, emptyParams);
        }

        public void logTiming(Throwable t, String msg, Object... params) {
            log(Debug.TIMING, t, msg, params);
        }

        public boolean infoOn() {
            return isOn(Debug.INFO);
        }

        public void logInfo(String msg) {
            log(Debug.INFO, null, msg, emptyParams);
        }

        public void logInfo(String msg, Object... params) {
            log(Debug.INFO, null, msg, params);
        }

        public void logInfo(Throwable t) {
            log(Debug.INFO, t, null, emptyParams);
        }

        public void logInfo(Throwable t, String msg) {
            log(Debug.INFO, t, msg, emptyParams);
        }

        public void logInfo(Throwable t, String msg, Object... params) {
            log(Debug.INFO, t, msg, params);
        }

        public boolean importantOn() {
            return isOn(Debug.IMPORTANT);
        }

        public void logImportant(String msg) {
            log(Debug.IMPORTANT, null, msg, emptyParams);
        }

        public void logImportant(String msg, Object... params) {
            log(Debug.IMPORTANT, null, msg, params);
        }

        public void logImportant(Throwable t) {
            log(Debug.IMPORTANT, t, null, emptyParams);
        }

        public void logImportant(Throwable t, String msg) {
            log(Debug.IMPORTANT, t, msg, emptyParams);
        }

        public void logImportant(Throwable t, String msg, Object... params) {
            log(Debug.IMPORTANT, t, msg, params);
        }

        public boolean warningOn() {
            return isOn(Debug.WARNING);
        }

        public void logWarning(String msg) {
            log(Debug.WARNING, null, msg, emptyParams);
        }

        public void logWarning(String msg, Object... params) {
            log(Debug.WARNING, null, msg, params);
        }

        public void logWarning(Throwable t) {
            log(Debug.WARNING, t, null, emptyParams);
        }

        public void logWarning(Throwable t, String msg) {
            log(Debug.WARNING, t, msg, emptyParams);
        }

        public void logWarning(Throwable t, String msg, Object... params) {
            log(Debug.WARNING, t, msg, params);
        }

        public boolean errorOn() {
            return isOn(Debug.ERROR);
        }

        public void logError(String msg) {
            log(Debug.ERROR, null, msg, emptyParams);
        }

        public void logError(String msg, Object... params) {
            log(Debug.ERROR, null, msg, params);
        }

        public void logError(Throwable t) {
            log(Debug.ERROR, t, null, emptyParams);
        }

        public void logError(Throwable t, String msg) {
            log(Debug.ERROR, t, msg, emptyParams);
        }

        public void logError(Throwable t, String msg, Object... params) {
            log(Debug.ERROR, t, msg, params);
        }

        public boolean fatalOn() {
            return isOn(Debug.FATAL);
        }

        public void logFatal(String msg) {
            log(Debug.FATAL, null, msg, emptyParams);
        }

        public void logFatal(String msg, Object... params) {
            log(Debug.FATAL, null, msg, params);
        }

        public void logFatal(Throwable t) {
            log(Debug.FATAL, t, null, emptyParams);
        }

        public void logFatal(Throwable t, String msg) {
            log(Debug.FATAL, t, msg, emptyParams);
        }

        public void logFatal(Throwable t, String msg, Object... params) {
            log(Debug.FATAL, t, msg, params);
        }
    }

    /**
     * Returns a new OfbizLogger wrapper logger around the given log4j
     * {@link org.apache.logging.log4j.Logger} instance.
     */
    public static OfbizLogger getOfbizLogger(Logger log4jLogger) {
        return OfbizLogger.getInstance(log4jLogger);
    }

    /**
     * Returns a new OfbizLogger wrapper logger for the given Ofbiz
     * Java class.
     * <p>
     * For Java, the class can be gotten by invoking
     * <code>java.lang.invoke.MethodHandles.lookup().lookupClass()</code>
     * in the calling class.
     */
    public static OfbizLogger getOfbizLogger(Class<?> cls) {
        return OfbizLogger.getInstance(cls);
    }

    /**
     * Returns a new OfbizLogger wrapper logger for the given Ofbiz
     * Java or Groovy class module name.
     * <p>
     * For Java, the module name can be gotten by invoking
     * <code>java.lang.invoke.MethodHandles.lookup().lookupClass()</code>
     * in the calling class.
     * <p>
     * For Groovy, the module name can simply be specified as "MyScriptName.groovy".
     */
    public static OfbizLogger getOfbizLogger(String module) {
        return OfbizLogger.getInstance(module);
    }

    /**
     * Returns the root OfbizLogger logger wrapper.
     */
    public static OfbizLogger getOfbizLogger() {
        return OfbizLogger.getInstance();
    }

    /*
     * *********************************************************************
     * SCIPIO: OfbizLogger overloads
     * *********************************************************************
     * These provide compatibility with the legacy ofbiz Debug log line invocation interface,
     * when using the OfbizLogger instance from a class (instead of the legacy module String).
     * NOTE: 2018-05-24: For the foreseeable future, it is recommended to use these
     * instead of the OfbizLogger methods, for consistency reason. These do not add any overhead.
     */

    public static void log(int level, String msg, OfbizLogger logger) {
        log(level, null, msg, logger, "org.ofbiz.base.util.Debug", emptyParams);
    }

    public static void log(int level, Throwable t, String msg, OfbizLogger logger) {
        log(level, t, msg, logger, "org.ofbiz.base.util.Debug", emptyParams);
    }

    public static void log(int level, Throwable t, String msg, OfbizLogger logger, Object... params) {
        log(level, t, msg, logger, "org.ofbiz.base.util.Debug", params);
    }

    public static void log(int level, Throwable t, String msg, OfbizLogger logger, String callingClass) {
        log(level, t, msg, logger, callingClass, new Object[0]);
    }

    public static void log(int level, Throwable t, String msg, OfbizLogger logger, String callingClass, Object... params) {
        if (isOn(level) && isOnForThread(level)) { // SCIPIO: 2018-10-12: isOnForThread
            if (msg != null && params.length > 0) {
                StringBuilder sb = new StringBuilder();
                Formatter formatter = new Formatter(sb);
                formatter.format(msg, params);
                msg = sb.toString();
                formatter.close();
            }

            // log
            logger.log4jLogger.log(levelObjs[level], msg, t);
        }
    }

    public static void log(String msg, OfbizLogger module, Object... params) {
        log(Debug.ALWAYS, null, msg, module, params);
    }

    public static void log(Throwable t, OfbizLogger module) {
        log(Debug.ALWAYS, t, null, module, emptyParams);
    }

    public static void log(Throwable t, String msg, OfbizLogger module) {
        log(Debug.ALWAYS, t, msg, module, emptyParams);
    }

    public static void log(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.ALWAYS, t, msg, module, params);
    }

    public static void logVerbose(String msg, OfbizLogger module) {
        log(Debug.VERBOSE, null, msg, module, emptyParams);
    }

    public static void logVerbose(String msg, OfbizLogger module, Object... params) {
        log(Debug.VERBOSE, null, msg, module, params);
    }

    public static void logVerbose(Throwable t, OfbizLogger module) {
        log(Debug.VERBOSE, t, null, module, emptyParams);
    }

    public static void logVerbose(Throwable t, String msg, OfbizLogger module) {
        log(Debug.VERBOSE, t, msg, module, emptyParams);
    }

    public static void logVerbose(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.VERBOSE, t, msg, module, params);
    }

    public static void logTiming(String msg, OfbizLogger module) {
        log(Debug.TIMING, null, msg, module, emptyParams);
    }

    public static void logTiming(String msg, OfbizLogger module, Object... params) {
        log(Debug.TIMING, null, msg, module, params);
    }

    public static void logTiming(Throwable t, OfbizLogger module) {
        log(Debug.TIMING, t, null, module, emptyParams);
    }

    public static void logTiming(Throwable t, String msg, OfbizLogger module) {
        log(Debug.TIMING, t, msg, module, emptyParams);
    }

    public static void logTiming(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.TIMING, t, msg, module, params);
    }

    public static void logInfo(String msg, OfbizLogger module) {
        log(Debug.INFO, null, msg, module, emptyParams);
    }

    public static void logInfo(String msg, OfbizLogger module, Object... params) {
        log(Debug.INFO, null, msg, module, params);
    }

    public static void logInfo(Throwable t, OfbizLogger module) {
        log(Debug.INFO, t, null, module, emptyParams);
    }

    public static void logInfo(Throwable t, String msg, OfbizLogger module) {
        log(Debug.INFO, t, msg, module, emptyParams);
    }

    public static void logInfo(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.INFO, t, msg, module, params);
    }

    public static void logImportant(String msg, OfbizLogger module) {
        log(Debug.IMPORTANT, null, msg, module, emptyParams);
    }

    public static void logImportant(String msg, OfbizLogger module, Object... params) {
        log(Debug.IMPORTANT, null, msg, module, params);
    }

    public static void logImportant(Throwable t, OfbizLogger module) {
        log(Debug.IMPORTANT, t, null, module, emptyParams);
    }

    public static void logImportant(Throwable t, String msg, OfbizLogger module) {
        log(Debug.IMPORTANT, t, msg, module, emptyParams);
    }

    public static void logImportant(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.IMPORTANT, t, msg, module, params);
    }

    public static void logWarning(String msg, OfbizLogger module) {
        log(Debug.WARNING, null, msg, module, emptyParams);
    }

    public static void logWarning(String msg, OfbizLogger module, Object... params) {
        log(Debug.WARNING, null, msg, module, params);
    }

    public static void logWarning(Throwable t, OfbizLogger module) {
        log(Debug.WARNING, t, null, module, emptyParams);
    }

    public static void logWarning(Throwable t, String msg, OfbizLogger module) {
        log(Debug.WARNING, t, msg, module, emptyParams);
    }

    public static void logWarning(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.WARNING, t, msg, module, params);
    }

    public static void logError(String msg, OfbizLogger module) {
        log(Debug.ERROR, null, msg, module, emptyParams);
    }

    public static void logError(String msg, OfbizLogger module, Object... params) {
        log(Debug.ERROR, null, msg, module, params);
    }

    public static void logError(Throwable t, OfbizLogger module) {
        log(Debug.ERROR, t, null, module, emptyParams);
    }

    public static void logError(Throwable t, String msg, OfbizLogger module) {
        log(Debug.ERROR, t, msg, module, emptyParams);
    }

    public static void logError(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.ERROR, t, msg, module, params);
    }

    public static void logFatal(String msg, OfbizLogger module) {
        log(Debug.FATAL, null, msg, module, emptyParams);
    }

    public static void logFatal(String msg, OfbizLogger module, Object... params) {
        log(Debug.FATAL, null, msg, module, params);
    }

    public static void logFatal(Throwable t, OfbizLogger module) {
        log(Debug.FATAL, t, null, module, emptyParams);
    }

    public static void logFatal(Throwable t, String msg, OfbizLogger module) {
        log(Debug.FATAL, t, msg, module, emptyParams);
    }

    public static void logFatal(Throwable t, String msg, OfbizLogger module, Object... params) {
        log(Debug.FATAL, t, msg, module, params);
    }

    /**
     * SCIPIO: For internal framework use only - do not use - may be removed at any time. (2018-10-12)
     */
    private static boolean isOnForThread(int level) {
        return (!levelsWithThreadSupport[level] || threadLevelAllowCache.get()[level]);
    }

//    /**
//     * SCIPIO: For internal framework use only - do not use - may be removed at any time. (2018-10-12)
//     */
//    public static void setThreadLevelAllowCache(boolean[] allowLevels) {
//        threadLevelAllowCache.set(allowLevels);
//    }

    /**
     * SCIPIO: For internal framework use only - do not use - may be removed at any time. (2018-10-12)
     */
    public static void setThreadLevelDisableWarningError() {
        threadLevelAllowCache.set(levelAllowNoWarningError);
    }

    /**
     * SCIPIO: For internal framework use only - do not use - may be removed at any time. (2018-10-12)
     */
    public static void setThreadLevelDisableError() {
        threadLevelAllowCache.set(levelAllowNoError);
    }

    /**
     * SCIPIO: For internal framework use only - do not use - may be removed at any time. (2018-10-12)
     */
    public static void restoreThreadLevelAllow() {
        threadLevelAllowCache.remove();
    }

    /**
     * SCIPIO: Checks the current thread stack trace to find calling method that does not belong to the specified classes,
     * and returns its StackTraceElement (or null if all excluded).
     * The calling method is automatically excluded.
     * WARN: This method is very slow and should only be used if verbose on.
     * Added 2018-11-29.
     */
    public static StackTraceElement getCallerInfo(Collection<String> excludeClasses) {
        StackTraceElement[] stList = Thread.currentThread().getStackTrace();
        for(int i = 2; i < stList.length; i++) { // 2: skip this method and the caller
            StackTraceElement st = stList[i];
            if (!excludeClasses.contains(st.getClassName()) && !Debug.class.getName().equals(st.getClassName())) {
                return st;
            }
        }
        return null;
    }

    /**
     * SCIPIO: Checks the current thread stack trace to find calling method,
     * and returns its StackTraceElement (or null if all excluded).
     * The calling method is automatically excluded.
     * WARN: This method is very slow and should only be used if verbose on.
     * Added 2018-11-29.
     */
    public static StackTraceElement getCallerInfo() {
        return getCallerInfo(Collections.emptyList());
    }

    /**
     * SCIPIO: Checks the current thread stack trace to find calling method that does not belong to the specified classes,
     * and returns a short string with class simple name, method and line number (or empty string if all excluded).
     * The calling method is automatically excluded.
     * WARN: This method is very slow and should only be used if verbose on.
     * Added 2018-11-29.
     */
    public static String getCallerShortInfo(Collection<String> excludeClasses) {
        return formatCallerShortInfo(getCallerInfo(excludeClasses));
    }

    /**
     * SCIPIO: Checks the current thread stack trace to find calling method that does not belong to the specified classes,
     * and returns a short string with class simple name, method and line number (or empty string if all excluded).
     * The calling method is automatically excluded.
     * WARN: This method is very slow and should only be used if verbose on.
     * Added 2018-11-29.
     */
    public static String getCallerShortInfo(String... excludeClasses) {
        return getCallerShortInfo(Arrays.asList(excludeClasses));
    }

    /**
     * SCIPIO: Checks the current thread stack trace to find calling method,
     * and returns a short string with class simple name, method and line number (or empty string if all excluded).
     * The calling method is automatically excluded.
     * WARN: This method is very slow and should only be used if verbose on.
     * Added 2018-12-03.
     */
    public static String getCallerShortInfo() {
        return formatCallerShortInfo(getCallerInfo(Collections.emptyList()));
    }

    /**
     * SCIPIO: Formats StackTraceElement to a short string with class simple name, method and line number.
     * Added 2018-11-29.
     */
    public static String formatCallerShortInfo(StackTraceElement callerInfo) {
        if (callerInfo == null) {
            return "";
        }
        return callerInfo.getClassName().substring(callerInfo.getClassName().lastIndexOf('.') + 1) 
                + "." + callerInfo.getMethodName() + "@" + callerInfo.getLineNumber();
    }

    /**
     * SCIPIO: Returns the log locale. This is always {@link Locale#ENGLISH}.
     * <p>
     * NOTE: This should be used in Debug.logXxx calls which use UtilProperties.getMessage to prepare log-destined messages;
     * i.e. call UtilProperties.getMessage once with context locale to format a message for caller, and then call
     * UtilProperties.getMessage a second time with the log locale to print to the log. e.g.:
     * <pre>
     * {@code    Debug.logError("Something happened: " + UtilProperties.getMessage("CommonErrorUiLabels", "CommonErrorOccurredContactSupport", Debug.getLogLocale()), module);
     *    return ServiceUtil.returnError(UtilProperties.getMessage("CommonErrorUiLabels", "CommonErrorOccurredContactSupport", (Locale) context.get("locale"));
     * }
     * </pre>
     * <p>
     * Added 2018-12-18.
     */
    public static Locale getLogLocale() {
        return LOG_LOCALE;
    }

    /**
     * SCIPIO: DO NOT USE: Returns a "dummy" static instance, for use by <code>FreeMarkerWorker</code>.
     * Subject to change without notice.
     * Added 2019-01-31.
     */
    public static Debug getStaticInstance() {
        return INSTANCE;
    }
}