blob: 6298cf6332b301b27698e569ce0a7102aa593e69 [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* one or more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.internal.logging;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter.Result;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
import com.gemstone.gemfire.InternalGemFireError;
import com.gemstone.gemfire.distributed.internal.DistributionConfig;
import com.gemstone.gemfire.distributed.internal.InternalDistributedSystem;
import com.gemstone.gemfire.internal.logging.log4j.AppenderContext;
import com.gemstone.gemfire.internal.logging.log4j.ConfigLocator;
import com.gemstone.gemfire.internal.logging.log4j.Configurator;
import com.gemstone.gemfire.internal.logging.log4j.FastLogger;
import com.gemstone.gemfire.internal.logging.log4j.LogMarker;
import com.gemstone.gemfire.internal.logging.log4j.LogWriterLogger;
import com.gemstone.gemfire.internal.util.IOUtils;
import com.gemstone.org.apache.logging.log4j.core.config.xml.GemFireXmlConfiguration;
import com.gemstone.org.apache.logging.log4j.core.config.xml.GemFireXmlConfigurationFactory;
import com.gemstone.org.apache.logging.log4j.message.GemFireParameterizedMessageFactory;
/**
* Centralizes log configuration and initialization.
*
* @author bakera
* @author David Hoots
*/
@SuppressWarnings("unused")
public class LogService extends LogManager {
// This is highest point in the hierarchy for all GemFire logging
public static final String ROOT_LOGGER_NAME = "";
public static final String BASE_LOGGER_NAME = "com.gemstone";
public static final String MAIN_LOGGER_NAME = "com.gemstone.gemfire";
public static final String SECURITY_LOGGER_NAME = "com.gemstone.gemfire.security";
public static final String GEMFIRE_VERBOSE_FILTER = "{GEMFIRE_VERBOSE}";
protected static final String STDOUT = "STDOUT";
private static final PropertyChangeListener propertyChangeListener = new PropertyChangeListenerImpl();
public static final String DEFAULT_CONFIG = "/com/gemstone/gemfire/internal/logging/log4j/log4j2-default.xml";
public static final String CLI_CONFIG = "/com/gemstone/gemfire/internal/logging/log4j/log4j2-cli.xml";
/** Protected by static synchronization. Used for removal and adding stdout back in. */
private static Appender stdoutAppender;
/** Set with info for Banner to log anytime LogService is reinitialized */
private static String configFileInformation;
static {
init();
}
private static void init() {
setLog4jConfigFileProperty();
LoggerContext context = ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(BASE_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext();
context.reconfigure();
context.removePropertyChangeListener(propertyChangeListener);
context.addPropertyChangeListener(propertyChangeListener);
setFastLoggerDebugAvailableFlag();
configureLoggers(false, false);
}
public static void initialize() {
new LogService();
}
public static void reconfigure() {
init();
}
public static void configureLoggers(final boolean hasLogFile, final boolean hasSecurityLogFile) {
Configurator.getOrCreateLoggerConfig(BASE_LOGGER_NAME, true, false);
Configurator.getOrCreateLoggerConfig(MAIN_LOGGER_NAME, true, hasLogFile);
final boolean useMainLoggerForSecurity = !hasSecurityLogFile;
Configurator.getOrCreateLoggerConfig(SECURITY_LOGGER_NAME, useMainLoggerForSecurity, hasSecurityLogFile);
}
public static AppenderContext getAppenderContext() {
return new AppenderContext();
}
public static AppenderContext getAppenderContext(final String name) {
return new AppenderContext(name);
}
public static boolean isUsingGemFireDefaultConfig() {
final String configFileName = new StrSubstitutor(new Interpolator()).replace(
PropertiesUtil.getProperties().getStringProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY));
return configFileName == null ? false : configFileName.contains(DEFAULT_CONFIG);
}
/**
* Check to see if the user has specified a Log4j configuration file. If not, attempt
* to find a GemFire Log4j configuration file in various locations.
*/
private static final void setLog4jConfigFileProperty() {
// fix bug #52175
final URL configInClasspath = ConfigLocator.findConfigInClasspath();
if (configInClasspath != null ) {
// Log4J 2 will find the configuration file in classpath so do nothing
configFileInformation = "Using log4j configuration found in classpath: '" + configInClasspath.toString() + "'";
StatusLogger.getLogger().info(configFileInformation);
return;
}
// If the user set the log4j system property then there's nothing else to do.
final String configFileName = new StrSubstitutor(new Interpolator()).replace(
PropertiesUtil.getProperties().getStringProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY));
if (configFileName != null) {
final URL configUrl = LogService.class.getResource(configFileName);// log4j2-cli.xml is non-null, external is null
if (configUrl == null) {
//We will let log4j2 handle the null case and just log what file we are attempting to use
configFileInformation = "Using log4j configuration file specified by " + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY + ": '" + configFileName + "'";
StatusLogger.getLogger().info(configFileInformation);
return;
}
else {
//If the resource can be found and in cases where the resource is in gemfire jar,
//we set the log location to the file that was found
String configFilePropertyValue = configUrl.toString();
System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, configFilePropertyValue);
configFileInformation = "Using log4j configuration file specified by " + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY + ": '" + configFilePropertyValue + "'";
StatusLogger.getLogger().info(configFileInformation);
return;
}
}
// // set log4j.configurationFactory to be our optimized version
// final String factory = GemFireXmlConfigurationFactory.class.getName();
// System.setProperty(ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY, factory);
// StatusLogger.getLogger().debug("Using log4j configuration factory '{}'", factory);
// If one of the default log4j config files exists in the current directory then use it.
File log4jConfigFile = findLog4jConfigInCurrentDir();
if (log4jConfigFile != null) {
String filePath = IOUtils.tryGetCanonicalPathElseGetAbsolutePath(log4jConfigFile);
String value = new File(filePath).toURI().toString();
String configFilePropertyValue = new File(filePath).toURI().toString();
System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, configFilePropertyValue);
configFileInformation = "Setting " + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY + " to specify log4j configuration file in current directory: '" + configFilePropertyValue + "'";
StatusLogger.getLogger().debug(configFileInformation);
return;
}
// Use the log4j config file found on the classpath in the gemfire jar file.
final URL configUrl = LogService.class.getResource(DEFAULT_CONFIG);
String configFilePropertyValue = configUrl.toString();
System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, configFilePropertyValue);
configFileInformation = "Setting " + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY + " to specify log4j configuration file: '" + configFilePropertyValue + "'";
StatusLogger.getLogger().info(configFileInformation);
return;
}
public static String getConfigInformation() {
return configFileInformation;
}
/**
* Finds a Log4j configuration file in the current directory. The names of
* the files to look for are the same as those that Log4j would look for on
* the classpath.
*
* @return A File for the configuration file or null if one isn't found.
*/
public static File findLog4jConfigInCurrentDir() {
return ConfigLocator.findConfigInWorkingDirectory();
}
/**
* Returns a Logger with the name of the calling class.
* @return The Logger for the calling class.
*/
public static Logger getLogger() {
return new FastLogger(LogManager.getLogger(getClassName(2), GemFireParameterizedMessageFactory.INSTANCE));
}
public static Logger getLogger(final String name) {
return new FastLogger(LogManager.getLogger(name, GemFireParameterizedMessageFactory.INSTANCE));
}
/**
* Returns a LogWriterLogger that is decorated with the LogWriter and LogWriterI18n
* methods.
*
* This is the bridge to LogWriter and LogWriterI18n that we need to eventually
* stop using in phase 1. We will switch over from a shared LogWriterLogger instance
* to having every GemFire class own its own private static GemFireLogger
*
* @return The LogWriterLogger for the calling class.
*/
public static LogWriterLogger createLogWriterLogger(final String name, final String connectionName, final boolean isSecure) {
return LogWriterLogger.create(name, connectionName, isSecure);
}
/**
* Return the Log4j Level associated with the int level.
*
* @param intLevel
* The int value of the Level to return.
* @return The Level.
* @throws java.lang.IllegalArgumentException if the Level int is not registered.
*/
public static Level toLevel(final int intLevel) {
for (Level level : Level.values()) {
if (level.intLevel() == intLevel) {
return level;
}
}
throw new IllegalArgumentException("Unknown int level [" + intLevel + "].");
}
/**
* Gets the class name of the caller in the current stack at the given {@code depth}.
*
* @param depth a 0-based index in the current stack.
* @return a class name
*/
public static String getClassName(final int depth) {
return new Throwable().getStackTrace()[depth].getClassName();
}
public static void setFastLoggerDebugAvailableFlag() {
final Configuration config = ((org.apache.logging.log4j.core.Logger)
LogManager.getLogger(BASE_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration();
// Check for debug/trace and filters on each logger
for (LoggerConfig loggerConfig : config.getLoggers().values()) {
if (loggerConfig.getName().startsWith(BASE_LOGGER_NAME)
&& ((loggerConfig.hasFilter() && !GEMFIRE_VERBOSE_FILTER.equals(loggerConfig.getFilter().toString()))
|| loggerConfig.getLevel().isLessSpecificThan(Level.DEBUG))){
FastLogger.setDebugAvailable(true);
return;
}
}
// Check for context filters
if (config.hasFilter()) {
FastLogger.setDebugAvailable(true);
} else {
FastLogger.setDebugAvailable(false);
}
}
private static class PropertyChangeListenerImpl implements PropertyChangeListener {
@SuppressWarnings("synthetic-access")
@Override
public void propertyChange(final PropertyChangeEvent evt) {
StatusLogger.getLogger().debug("LogService responding to a property change event. Property name is {}.",
evt.getPropertyName());
if (evt.getPropertyName().equals(LoggerContext.PROPERTY_CONFIG)) {
setFastLoggerDebugAvailableFlag();
}
}
}
public static void setBaseLogLevel(Level level) {
if (isUsingGemFireDefaultConfig()) {
Configurator.setLevel(ROOT_LOGGER_NAME, level);
}
Configurator.setLevel(BASE_LOGGER_NAME, level);
Configurator.setLevel(MAIN_LOGGER_NAME, level);
}
public static void setSecurityLogLevel(Level level) {
Configurator.setLevel(SECURITY_LOGGER_NAME, level);
}
public static Level getBaseLogLevel() {
return Configurator.getLevel(BASE_LOGGER_NAME);
}
public static LoggerConfig getRootLoggerConfig() {
//return ((org.apache.logging.log4j.core.Logger)LogService.getLogger()).getContext().getConfiguration().getLoggerConfig(LogManager.getRootLogger().getName());
return Configurator.getLoggerConfig(LogManager.getRootLogger().getName());
}
/**
* Removes STDOUT ConsoleAppender from ROOT logger. Only called when using
* the log4j2-default.xml configuration. This is done when creating the
* LogWriterAppender for log-file. The Appender instance is stored in
* stdoutAppender so it can be restored later using restoreConsoleAppender.
*/
public static synchronized void removeConsoleAppender() {
final AppenderContext appenderContext = LogService.getAppenderContext(LogService.ROOT_LOGGER_NAME);
final LoggerConfig config = appenderContext.getLoggerConfig();
Appender stdout = config.getAppenders().get(STDOUT);
if (stdout != null) {
config.removeAppender(STDOUT);
stdoutAppender = stdout;
appenderContext.getLoggerContext().updateLoggers();
}
}
/**
* Restores STDOUT ConsoleAppender to ROOT logger. Only called when using
* the log4j2-default.xml configuration. This is done when the
* LogWriterAppender for log-file is destroyed. The Appender instance stored
* in stdoutAppender is used.
*/
public static synchronized void restoreConsoleAppender() {
if (stdoutAppender == null) {
return;
}
final AppenderContext appenderContext = LogService.getAppenderContext(LogService.ROOT_LOGGER_NAME);
final LoggerConfig config = appenderContext.getLoggerConfig();
Appender stdout = config.getAppenders().get(STDOUT);
if (stdout == null) {
config.addAppender(stdoutAppender, Level.ALL, null);
appenderContext.getLoggerContext().updateLoggers();
}
}
private LogService() {
// do not instantiate
}
}