All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.kernel.health.Log Maven / Gradle / Ivy

/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.health;

import com.google.common.collect.Lists;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import sirius.kernel.Sirius;
import sirius.kernel.async.CallContext;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.PartCollection;
import sirius.kernel.di.std.Parts;
import sirius.kernel.nls.NLS;

import java.util.Collections;
import java.util.List;

/**
 * The logging facade used by the system.
 * 

* Note: Instead of "just" logging exceptions, handle them with {@link sirius.kernel.health.Exceptions#handle()} * to generate sophisticated error messages and to permit other parts of the framework to intercept error * handling. *

* In contrast to other approaches, it is not recommended to create a logger per class, but rather one per * framework or sub system. It should have a concise name, all lowercase without any dots. The log level of each * logger is read from the configuration using {@code logging.[NAME]}. It may be set to one of: *

    *
  • DEBUG
  • *
  • INFO
  • *
  • WARN
  • *
  • ERROR
  • *
*

* Internally uses log4j to perform all logging operations. Still it is recommended to only log through this facade * and not to rely on any log4j specific behaviour. */ @SuppressWarnings("squid:S00100") @Explain("We have these special method names so they stand out from the business logic.") public class Log { private final Logger logger; private Boolean fineLogging; private static final List all = Lists.newCopyOnWriteArrayList(); /** * Provides a generic logger for application log messages. *

* This should ony be used by application code. Use a specific logger if possible and appropriate. *

* Use {@link #SYSTEM} for generic framework / system specific events. */ public static final Log APPLICATION = Log.get("application"); /** * Provides a generic logger for system log messages. *

* This should only be used by libraries or frameworks. Use a specific logger if possible and appropriate. *

* Use {@link #APPLICATION} for generic application specific events. */ public static final Log SYSTEM = Log.get("system"); /** * Provides a logger used to log events of the background / batch execution system, like {@link sirius.kernel.async.BackgroundLoop background loops}. *

* Use a specific logger if possible and appropriate. */ public static final Log BACKGROUND = Log.get("background"); /** * Used to cut endless loops while feeding taps */ private static ThreadLocal frozen = new ThreadLocal<>(); @Parts(LogTap.class) private static PartCollection taps; /* * Use get(String) to create a new instance */ private Log(Logger logger) { super(); this.logger = logger; } /** * Generates a new logger with the given name *

* The given name should be short and simple. It is not recommended to create a logger per class but one for * each framework or subsystem. * * @param name the name of the logger. This should be a simple name, completely lowercase, without any dots * @return a new logger logging with the given name. */ @SuppressWarnings("squid:S2250") @Explain("Loggers are only created once, so there is no performance hotspot") public static synchronized Log get(String name) { Log result = new Log(Logger.getLogger(name)); all.add(result); if (!name.matches("[a-z0-9\\-]+")) { result.WARN("Invalid logger name: %s. Only numbers, lowercase letters and - are allowed!%n", name); } return result; } /** * Returns a list of all known loggers. * * @return a list of all known loggers */ public static List getAllLoggers() { return Collections.unmodifiableList(all); } /** * Helper method the change the log-level for a given logger. *

* By default the system configuration is used to set the leg level (logging.[name]=LEVEL). Also the "logger" * command in the console can be used to change the log level at runtime. * * @param logger the name of the logger to change * @param level the desired log level */ public static void setLevel(String logger, Level level) { // Setup log4j Logger.getLogger(logger).setLevel(level); // Setup java.util.logging java.util.logging.Logger.getLogger(logger).setLevel(convertLog4jLevel(level)); // Clear cached "isFINE" flag to be consistently re-computed on the next access. for (Log log : all) { if (log.getName().equals(logger)) { log.fineLogging = null; } } } /** * Converts a given java.util.logging.Level to a log4j level. * * @param juliLevel the java.util.logging level * @return the converted equivalent for log4j */ public static Level convertJuliLevel(java.util.logging.Level juliLevel) { if (juliLevel.equals(java.util.logging.Level.FINEST)) { return Level.TRACE; } if (juliLevel.equals(java.util.logging.Level.FINER)) { return Level.DEBUG; } if (juliLevel.equals(java.util.logging.Level.FINE)) { return Level.DEBUG; } if (juliLevel.equals(java.util.logging.Level.INFO)) { return Level.INFO; } if (juliLevel.equals(java.util.logging.Level.WARNING)) { return Level.WARN; } if (juliLevel.equals(java.util.logging.Level.SEVERE)) { return Level.ERROR; } if (juliLevel.equals(java.util.logging.Level.ALL)) { return Level.ALL; } if (juliLevel.equals(java.util.logging.Level.OFF)) { return Level.OFF; } return Level.DEBUG; } /** * Converts a given log4j to a java.util.logging.Level level. * * @param log4jLevel the log4j level * @return the converted equivalent java.util.logging */ public static java.util.logging.Level convertLog4jLevel(Level log4jLevel) { if (log4jLevel.equals(Level.TRACE)) { return java.util.logging.Level.FINEST; } if (log4jLevel.equals(Level.DEBUG)) { return java.util.logging.Level.FINER; } if (log4jLevel.equals(Level.INFO)) { return java.util.logging.Level.INFO; } if (log4jLevel.equals(Level.WARN)) { return java.util.logging.Level.WARNING; } if (log4jLevel.equals(Level.ERROR)) { return java.util.logging.Level.SEVERE; } if (log4jLevel.equals(Level.FATAL)) { return java.util.logging.Level.SEVERE; } if (log4jLevel.equals(Level.ALL)) { return java.util.logging.Level.ALL; } if (log4jLevel.equals(Level.OFF)) { return java.util.logging.Level.OFF; } return java.util.logging.Level.FINE; } /** * Logs the given message at INFO level *

* The given object is converted to a string if necessary. The INFO level should be used for informative * messages to the system operator which occur at a low rate * * @param msg the message to be logged */ public void INFO(Object msg) { if (msg == null) { return; } if (logger.isInfoEnabled()) { fixMDC(); if (msg instanceof Throwable) { logger.info(((Throwable) msg).getMessage(), (Throwable) msg); } else { logger.info(msg.toString()); } tap(msg, true, Level.INFO); } else { tap(msg, false, Level.INFO); } } private void fixMDC() { if (logger.isDebugEnabled() || Sirius.isDev() || Sirius.isStartedAsTest()) { CallContext callContext = CallContext.getCurrent(); MDC.put("flow", "|" + callContext.getWatch().elapsedMillis() + "ms"); } } /** * Used to log the given message msg at INFO level if debug mode is enabled * ({@link sirius.kernel.Sirius#isDev()}). Otherwise the message will be logged as FINE. * * @param msg the message to log */ public void DEBUG_INFO(Object msg) { if (Sirius.isDev()) { INFO(msg); } else { FINE(msg); } } /* * Notify all log taps */ private void tap(Object msg, boolean wouldLog, Level level) { if (Boolean.TRUE.equals(frozen.get())) { return; } try { frozen.set(Boolean.TRUE); if (taps != null) { for (LogTap tap : taps) { invokeTap(msg, wouldLog, level, tap); } } } finally { frozen.set(Boolean.FALSE); } } private void invokeTap(Object msg, boolean wouldLog, Level level, LogTap tap) { try { tap.handleLogMessage(new LogMessage(NLS.toUserString(msg), level, this, wouldLog, Thread.currentThread().getName())); } catch (Exception e) { // Ignored - if we can't log s.th. let's just give up... } } /** * Formats the given message at the INFO level using the supplied parameters. *

* The INFO level should be used for informative messages to the system operator which occur at a low rate * * @param msg the message containing placeholders as understood by {@link Strings#apply(String, Object...)} * @param params the parameters used to format the resulting log message */ public void INFO(String msg, Object... params) { msg = Strings.apply(msg, params); if (logger.isInfoEnabled()) { fixMDC(); logger.info(msg); tap(msg, true, Level.INFO); } else { tap(msg, false, Level.INFO); } } /** * Logs the given message at the FINE level *

* The given object is converted to a string if necessary. The FINE level can be used for in depth debug or trace * messages used when developing a system. Sill the rate should be kept bearable to enable this level in * production systems to narrow down errors. * * @param msg the message to be logged */ public void FINE(Object msg) { if (logger.isDebugEnabled()) { fixMDC(); if (msg instanceof Throwable) { logger.debug(((Throwable) msg).getMessage(), (Throwable) msg); } else { logger.debug(NLS.toUserString(msg)); } tap(msg, true, Level.DEBUG); } else { tap(msg, false, Level.DEBUG); } } /** * Formats the given message at the FINE level using the supplied parameters. *

* The FINE level can be used for in depth debug or trace messages used when developing a system. * Sill the rate should be kept bearable to enable this level in production systems to narrow down errors. * * @param msg the message containing placeholders as understood by {@link Strings#apply(String, Object...)} * @param params the parameters used to format the resulting log message */ public void FINE(String msg, Object... params) { msg = Strings.apply(msg, params); if (logger.isDebugEnabled()) { fixMDC(); logger.debug(msg); tap(msg, true, Level.DEBUG); } else { tap(msg, false, Level.DEBUG); } } /** * Logs the given message at the WARN level *

* The given object is converted to a string if necessary. The WARN level can be used to signal unexpected * situations which do not (yet) result in an error or problem. * * @param msg the message to be logged */ public void WARN(Object msg) { fixMDC(); if (msg instanceof Throwable) { logger.warn(((Throwable) msg).getMessage(), (Throwable) msg); } else { logger.warn(NLS.toUserString(msg)); } tap(msg, true, Level.WARN); } /** * Formats the given message at the WARN level using the supplied parameters. *

* The WARN level can be used to signal unexpected situations which do not (yet) result in an error or problem. * * @param msg the message containing placeholders as understood by {@link Strings#apply(String, Object...)} * @param params the parameters used to format the resulting log message */ public void WARN(String msg, Object... params) { msg = Strings.apply(msg, params); fixMDC(); logger.warn(msg); tap(msg, true, Level.WARN); } /** * Logs the given message at the SEVERE or ERROR level *

* The given object is converted to a string if necessary. The ERROR level can be used to signal problems or error * which occurred in the system. It is recommended to handle exceptions using {@link Exceptions} - which will * eventually also call this method, but provides sophisticated error handling. * * @param msg the message to be logged */ public void SEVERE(Object msg) { fixMDC(); if (msg instanceof Throwable) { logger.error(((Throwable) msg).getMessage(), (Throwable) msg); } else { logger.error(NLS.toUserString(msg)); } tap(msg, true, Level.ERROR); } /** * Determines if FINE message will be logged. *

* This can be used to decide whether "expensive" log messages should be constructed at all. Using * {@link #FINE(String, Object...)} doesn't require this check since the message is only formatted if it will be * logged. However, if the computation of one of the parameters is complex, one might sill want to surround the * log message by an appropriate if statement calling this method. * * @return true if this logger logs FINE message, false otherwise */ public boolean isFINE() { if (fineLogging == null) { fineLogging = logger.isDebugEnabled(); } return fineLogging; } /** * Returns the name of this logger * * @return the name supplied by {@link #get(String)}. */ public String getName() { return logger.getName(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy