com.sleepycat.je.utilint.LoggerUtils Maven / Gradle / Ivy
The newest version!
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.utilint;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.config.ConfigParam;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.Trace;
/**
* Logging Architecture
* ===========================
* JE uses the java.util.logging package. The ability to dynamically specify
* logging levels per component is important functionality for the system.
* Logging output is directed to the console, to the je.info files, and in
* special cases, to a MemoryHandler. The latter is meant for debugging and
* field support.
*
* Logging output from multiple environments may end up going to the same
* handler, either because a single process is executing multiple environments,
* or because the output of multiple environments, such as a replication group,
* is combined in a single display. Because of that, it's important for logging
* output to be prefixed with an environment id so it can be distinguished by
* environment.
*
* Loggers managed by java.util.logging.LogManager are supposed to be
* maintained with a weak reference by the LogManager. In our experience,
* loggers do not seem to be released, and seem to accumulate in
* memory. Because of that, we create a single logger per JE class, rather than
* a logger per class instance.
*
* The latter would be more convenient, because we wish to use environment
* specific information, such as the environment name as a prefix, or the
* location of the je.info file, when creating output. Restricting ourselves to
* a single per-class logger requires that we keep the logger and its
* associated handlers and formatters stateless, because the logger may be
* shared by multiple environments. To compensate for that, we use per-thread
* state to permit per-environment customization of the logging output (that is
* the logging prefix) and file handler location. Because we've seen some
* performance issues with ThreadLocals, we elected instead to maintain a
* per-thread map to store state information needed by the logger.
*
* This state information is:
*
* - the environment impl from the envMap(from which one can obtain the prefix
* and the console, file and memory handlers to use)
*
* - or if the environment impl is null because the component executes without
* an environment, the output will go to only a console handler. It will use a
* particular formatter to prefix the output with a useful id. This is obtained
* from the formatter map.
*
*
* With this scheme, a JE process has a maximum of
* - N loggers, where N is the number of classes which get loggers
* - 3 handlers * number of environments, because each environment creates
* a Console, File and Memory handler.
*
* How To Use Logging in a JE Class
* =======================================
* Creating a Logger: There are three kinds of loggers that a class may chose
* to use.
*
* 1. A class with a reference to EnvironmentImpl or RepImpl should use
* {@literal LoggerUtils.getLogger(Class>)} to create a logger which prefixes
* its output with an environment id. When a logger is obtained this way, the
* logger should not be used directly. Instead, LoggerUtils provides several
* methods like this:
* LoggerUtils.severe() equals to logger.severe
* LoggerUtils.warning() equals to logger.warning
* etc
* LoggerUtils.logMsg(Logger, EnvironmentImpl, Level, String) equals to
* logger.log(Level, String)
*
* 2. A class without an EnvironmentImpl which still has some kind of custom
* information to prepend to the logging output should use
* LoggerUtils.getFormatterNeeded(). For example,
* com.sleepycat.je.rep.monitor.Monitor does not have an environment, but does
* have a NameIdPair, and it can insert that information via a specific
* Formatter. When using this logger, the class must create and maintain a
* Formatter instance to pass as a logging parameter. When using this flavor,
* use:
* LoggerUtils.logMsg(Logger, Formatter, Level, String) where the
* formatter is the one created by the using class.
*
* 3. A logger without an EnvironmentImpl does not prefix or customize the
* logging output, and uses LoggerUtils.getLoggerFixedPrefix to create a
* logger. In this case, use the usual java.util.logging.Logger logging
* methods.
*
* Note: there are some JE classes which only conditionally reference an
* environment. In that case, the environment must also conditionally create
* a logger, and then use the wrapper methods which use both an environmentImpl
* and a formatter. For example:
*
* if (envImpl != null) {
* logger = LoggerUtils.getLogger(getClass());
* } else {
* logger = LoggerUtils.getLoggerFormatterNeeded();
* }
* formatter = new Formatter(.....);
*
* Then use LoggerUtils.logMsg(Logger, EnvironmentImpl, Formatter, Level,
* String) instead of Logger.log(Level, String)
*/
public class LoggerUtils {
/*
* Environment state to be used by a logger. Must be set and released
* per logger call.
*/
static final Map envMap =
new ConcurrentHashMap();
/*
* Formatter state to be used by a logger. Must be set and released
* per logger call. Used by logging calls that do not have an available
* environment.
*/
static final Map formatterMap =
new ConcurrentHashMap();
public static final String NO_ENV = ".noEnv";
public static final String FIXED_PREFIX = ".fixedPrefix";
private static final String PUSH_LEVEL = ".push.level";
/* Used to prevent multiple full thread dumps. */
private static final Object fullThreadDumpMutex = new Object();
/**
* Get a logger which is configured to use the shared console, memory, and
* file handlers of an EnvironmentImpl and prefixes all messages with an
* environment identifier. Use this for classes which have a reference
* to an EnvironmentImpl (or RepImpl).
*
* When a logger is obtained this way, the logger should not be used
* directly. Instead, the wrapper methods in LoggerUtils which put and
* remove the environment from the envMap must be used, so that the logging
* output can be properly prefixed and redirected to the correct
* environment.
*/
public static Logger getLogger(Class> cl) {
Logger logger = createLogger(cl.getName());
/* Check whether the logger already has existing handlers. */
boolean hasConsoleHandler = false;
boolean hasFileHandler = false;
boolean hasConfiguredHandler = false;
/*
* [#18277] Add null check of logger.getHandlers() because the Resin
* app server's implementation of logging can return null instead of an
* empty array.
*/
Handler[] handlers = logger.getHandlers();
if (handlers != null) {
for (Handler h : handlers) {
/*
* Intentionally check for java.util.logging.ConsoleHandler
* rather than ConsoleRedirectHandler, because the loggers that
* do not have a custom prefix use the ConsoleHandler
* directly. Having ConsoleRedirectHandler extend
* ConsoleHandler lets us have a model where the user only have
* to set com.sleepycat.je.util.ConsoleHandler in their logging
* properties file.
*/
if (h instanceof java.util.logging.ConsoleHandler) {
hasConsoleHandler = true;
}
if (h instanceof FileRedirectHandler) {
hasFileHandler = true;
}
if (h instanceof ConfiguredRedirectHandler) {
hasConfiguredHandler = true;
}
}
}
if (!hasConsoleHandler) {
logger.addHandler(new ConsoleRedirectHandler());
}
if (!hasFileHandler) {
logger.addHandler(new FileRedirectHandler());
}
if (!hasConfiguredHandler) {
logger.addHandler(new ConfiguredRedirectHandler());
}
return logger;
}
/**
* Get a logger which only publishes to a console handler. The logging
* output is prefixed in a custom way, using the formatter map to access
* the proper state. This should be used by a class that does not have
* an EnvironmentImpl, but still wishes to prepend some kind of custom
* prefix to the logging output.
*
* When a logger is obtained this way, the logger should not be used
* directly. Instead, the wrapper methods in LoggerUtils which use a
* Formatter parameter, and put and remove the environment from the
* formatterMap must be used, so that the logging output can be properly
* prefixed and redirected to the correct environment.
*/
public static Logger getLoggerFormatterNeeded(Class> cl) {
/*
* By convention, loggers that use redirect handlers are named with the
* class name. Name logger that don't use redirecting differently, in
* order to avoid conflicts when a single class uses both redirecting
* and fixed prefix loggers.
*/
Logger logger = createLogger(cl.getName() + NO_ENV);
/* Add a new handler if a console handler does not already exist. */
if (!hasConsoleHandler(logger)) {
logger.addHandler(new FormatterRedirectHandler());
}
return logger;
}
/* Convenience method for getLoggerFixedPrefix. */
public static Logger getLoggerFixedPrefix(Class> cl,
String prefix) {
return getLoggerFixedPrefix(cl, prefix, null);
}
/**
* Get a logger that uses the generic console handler, with no attempt to
* use thread local state to customize the message prefix.
*/
public static Logger getLoggerFixedPrefix(Class> cl,
String prefix,
EnvironmentImpl envImpl) {
/*
* By convention, loggers that use redirect handlers are named with the
* class name. Name logger that don't use redirecting differently, in
* order to avoid conflicts when a single class uses both redirecting
* and fixed prefix loggers.
*/
Logger logger = createLogger(cl.getName() + FIXED_PREFIX);
/* Check whether the logger already has this handler. */
if (!hasConsoleHandler(logger)) {
logger.addHandler(new com.sleepycat.je.util.ConsoleHandler
(new TracerFormatter(prefix), envImpl));
}
return logger;
}
/*
* Return true if this logger already has a console handler.
*/
private static boolean hasConsoleHandler(Logger logger) {
/*
* [#18277] Add null check of logger.getHandlers() because the Resin
* app server's implementation of logging can return null instead of an
* empty array.
*/
Handler[] handlers = logger.getHandlers();
if (handlers == null) {
return false;
}
for (Handler h : handlers) {
if (h instanceof java.util.logging.ConsoleHandler) {
return true;
}
}
return false;
}
/* Create a logger for the specified class name. */
private static Logger createLogger(String className) {
/*
* No need to set level values explicitly. This is managed in the
* standard way by java.util.logging.LogManager.
*/
Logger logger = Logger.getLogger(className);
/*
* We've debated permitting the logger to use parental handlers, which
* would permit using the standard java.util.logging policy of setting
* tbe property com.sleepycat.je.handlers as a way of customizing
* handlers. This was not useful because of the need to specify
* handlers per environment, and also caused a process monitor echo
* issue within NoSQL DB.
*/
logger.setUseParentHandlers(false);
return logger;
}
/* Get the value of a specified Logger property. */
public static String getLoggerProperty(String property) {
java.util.logging.LogManager mgr =
java.util.logging.LogManager.getLogManager();
return mgr.getProperty(property);
}
/**
* Get the push level for the MemoryHandler.
*/
public static Level getPushLevel(String name) {
String propertyValue = getLoggerProperty(name + PUSH_LEVEL);
Level level = Level.OFF;
if (propertyValue != null) {
level = Level.parse(propertyValue);
}
return level;
}
/**
* Log a message using this logger. We expect that this logger is one that
* has been configured to expect an environment. This utility method should
* be used to ensure that the thread specific context is pushed before
* logging, and cleared afterwards.
*/
public static void logMsg(Logger useLogger,
EnvironmentImpl envImpl,
Level logLevel,
String msg) {
/* Set thread specific context. */
if (envImpl != null) {
envMap.put(Thread.currentThread(), envImpl);
}
try {
useLogger.log(logLevel, msg);
} finally {
/* Clear thread specific context. */
envMap.remove(Thread.currentThread());
}
}
/**
* Log a message using the given RateLimitingLogger. We expect that this
* RLL wraps a Logger that has been configured to expect an environment.
* This utility method should be used to ensure that the thread specific
* context is pushed before logging, and cleared afterwards.
*/
public static void logMsg(RateLimitingLogger useLogger,
EnvironmentImpl envImpl,
T object,
Level logLevel,
String msg) {
/* Set thread specific context. */
if (envImpl != null) {
envMap.put(Thread.currentThread(), envImpl);
}
try {
useLogger.log(object, logLevel, msg);
} finally {
/* Clear thread specific context. */
envMap.remove(Thread.currentThread());
}
}
/**
* Use the environment logger.
*/
public static void envLogMsg(Level logLevel,
EnvironmentImpl envImpl,
String msg) {
logMsg(envImpl.getLogger(), envImpl, logLevel, msg);
}
/**
* Log a message using this logger. The logger may be either one that
* expects to use the state in the envMap (obtained via getLogger(), or it
* may be one that expects to use the state in the formatter map (obtained
* via getLoggerFormatterNeeded(). This method checks whether the
* EnvironmentImpl is null or not and choses the appropriate state type to
* use.
*/
public static void logMsg(Logger useLogger,
EnvironmentImpl envImpl,
Formatter formatter,
Level logLevel,
String msg) {
if (envImpl != null) {
logMsg(useLogger, envImpl, logLevel, msg);
} else {
logMsg(useLogger, formatter, logLevel, msg);
}
}
/* Some convenience methods. */
public static void severe(Logger useLogger,
EnvironmentImpl envImpl,
String msg) {
logMsg(useLogger, envImpl, Level.SEVERE, msg);
}
public static void warning(Logger useLogger,
EnvironmentImpl envImpl,
String msg) {
logMsg(useLogger, envImpl, Level.WARNING, msg);
}
public static void info(Logger useLogger,
EnvironmentImpl envImpl,
String msg) {
logMsg(useLogger, envImpl, Level.INFO, msg);
}
public static void fine(Logger useLogger,
EnvironmentImpl envImpl,
String msg) {
logMsg(useLogger, envImpl, Level.FINE, msg);
}
public static void finer(Logger useLogger,
EnvironmentImpl envImpl,
String msg) {
logMsg(useLogger, envImpl, Level.FINER, msg);
}
public static void finest(Logger useLogger,
EnvironmentImpl envImpl,
String msg) {
logMsg(useLogger, envImpl, Level.FINEST, msg);
}
/**
* Log a message with this logger. This utility method should be used in
* tandem with loggers obtained via getLoggerFormatterNeeded() to ensure
* that the thread specific Formatter is pushed before logging, and cleared
* afterwards.
*/
public static void logMsg(Logger useLogger,
Formatter formatter,
Level logLevel,
String msg) {
/* Set thread specific Formatter. */
if (formatter != null) {
formatterMap.put(Thread.currentThread(), formatter);
}
try {
useLogger.log(logLevel, msg);
} finally {
/* Clear thread specific Formatter. */
formatterMap.remove(Thread.currentThread());
}
}
/**
* Logger method for recording an exception and stacktrace to both the
* java.util.logging system and the .jdb files.
*/
public static void traceAndLogException(EnvironmentImpl envImpl,
String sourceClass,
String sourceMethod,
String msg,
Throwable t) {
String traceMsg = msg + "\n" + getStackTrace(t);
envMap.put(Thread.currentThread(), envImpl);
try {
envImpl.getLogger().logp
(Level.SEVERE, sourceClass, sourceMethod, traceMsg);
} finally {
envMap.remove(Thread.currentThread());
}
Trace.trace(envImpl, traceMsg);
}
/**
* Records a message both to the java.util.logging loggers and through
* the trace system which writes to the .jdb files. The logLevel parameter
* only applies to the java.util.logging system. Trace messages are
* unconditionally written to the .jdb files.
*
* Because of that, this method should be used sparingly, for critical
* messages.
*/
public static void traceAndLog(Logger logger,
EnvironmentImpl envImpl,
Level logLevel,
String msg) {
logMsg(logger, envImpl, logLevel, msg);
Trace.trace(envImpl, msg);
}
/** Return a String version of a stack trace */
public static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString();
stackTrace = stackTrace.replaceAll("<", "<");
stackTrace = stackTrace.replaceAll(">", ">");
return stackTrace;
}
/** Return the stack trace of the caller, for debugging. */
public static String getStackTrace() {
Exception e = new Exception();
return getStackTrace(e);
}
/* Get the level for ConsoleHandler and FileHandler. */
public static Level getHandlerLevel(DbConfigManager configManager,
ConfigParam param,
String levelName) {
boolean changed = false;
/* Check if the level params are set. */
String level = configManager.get(param);
if (!param.getDefault().equals(level)) {
changed = true;
}
/* Get the level from the java.util.logging configuration system. */
String propertyLevel = getLoggerProperty(levelName);
/*
* If the params are not set, and levels are set in the properties
* file, then set the level from properties file.
*/
if (!changed && propertyLevel != null) {
level = propertyLevel;
}
return Level.parse(level);
}
/**
* Logs a full thread dump as if jstack were piped to the je.info file.
*
* Only one dump per EnvironmentImpl lifetime is allowed. Allowing multiple
* dumps can causes them to be interleaved, and risks filling the je.info
* files with repeated dumps. The envImpl should be invalidated when this
* method is called, so one dump should be enough.
*/
public static void fullThreadDump(Logger logger,
EnvironmentImpl envImpl,
Level level) {
if (!logger.isLoggable(level)) {
return;
}
synchronized (fullThreadDumpMutex) {
if (envImpl != null) {
if (envImpl.getDidFullThreadDump()) {
return;
}
envImpl.setDidFullThreadDump(true);
}
Map stackTraces =
Thread.getAllStackTraces();
for (Map.Entry stme :
stackTraces.entrySet()) {
logMsg(logger, envImpl, level, stme.getKey().toString());
for (StackTraceElement ste : stme.getValue()) {
logMsg(logger, envImpl, level, " " + ste);
}
}
}
}
/**
* Displays both the exception class and the message. Use you wnat a
* relatively terse display of the exception (i.e. omitting stacktrace).
* Prefer to use this over exception.getMessage(), as some exceptions have
* null messages.
*/
public static String exceptionTypeAndMsg(Exception e) {
return e.getClass() + " : " + e.getMessage();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy