/* * 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.apache.geode.internal.logging; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; 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.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.lookup.StrLookup; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.status.StatusLogger; import org.apache.geode.internal.logging.log4j.AppenderContext; import org.apache.geode.internal.logging.log4j.ConfigLocator; import org.apache.geode.internal.logging.log4j.Configurator; import org.apache.geode.internal.logging.log4j.FastLogger; import org.apache.geode.internal.logging.log4j.LogWriterLogger; import org.apache.geode.internal.logging.log4j.message.GemFireParameterizedMessageFactory; /** * Centralizes log configuration and initialization. */ @SuppressWarnings("unused") public class LogService extends LogManager { // This is highest point in the hierarchy for all Geode logging public static final String ROOT_LOGGER_NAME = ""; public static final String BASE_LOGGER_NAME = "org.apache.geode"; public static final String MAIN_LOGGER_NAME = "org.apache.geode"; public static final String SECURITY_LOGGER_NAME = "org.apache.geode.security"; public static final String GEODE_VERBOSE_FILTER = "{GEODE_VERBOSE}"; public static final String GEMFIRE_VERBOSE_FILTER = "{GEMFIRE_VERBOSE}"; public static final String DEFAULT_CONFIG = "/log4j2.xml"; public static final String CLI_CONFIG = "/log4j2-cli.xml"; protected static final String STDOUT = "STDOUT"; private static final PropertyChangeListener propertyChangeListener = new PropertyChangeListenerImpl(); /** * Name of variable that is set to "true" in log4j2.xml to indicate that it is the default geode * config xml. */ private static final String GEMFIRE_DEFAULT_PROPERTY = "geode-default"; /** * Protected by static synchronization. Used for removal and adding stdout back in. */ private static Appender stdoutAppender; static { init(); } private LogService() { // do not instantiate } private static void init() { LoggerContext context = ((org.apache.logging.log4j.core.Logger) LogManager .getLogger(BASE_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext(); context.removePropertyChangeListener(propertyChangeListener); context.addPropertyChangeListener(propertyChangeListener); context.reconfigure(); // propertyChangeListener invokes configureFastLoggerDelegating 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 Configuration config = ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(ROOT_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration(); final StrSubstitutor sub = config.getStrSubstitutor(); final StrLookup resolver = sub.getVariableResolver(); final String value = resolver.lookup(GEMFIRE_DEFAULT_PROPERTY); return "true".equals(value); } public static String getConfigInformation() { return getConfiguration().getConfigurationSource().toString(); } /** * 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. * <p> * 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 Configuration getConfiguration() { final Configuration config = ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(ROOT_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration(); return config; } public static void configureFastLoggerDelegating() { final Configuration config = ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(ROOT_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration(); if (Configurator.hasContextWideFilter(config) || Configurator.hasAppenderFilter(config) || Configurator.hasDebugOrLower(config) || Configurator.hasLoggerFilter(config) || Configurator.hasAppenderRefFilter(config)) { FastLogger.setDelegating(true); } else { FastLogger.setDelegating(false); } } public static void setSecurityLogLevel(Level level) { Configurator.setLevel(SECURITY_LOGGER_NAME, level); } public static Level getBaseLogLevel() { return Configurator.getLevel(BASE_LOGGER_NAME); } 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 LoggerConfig getRootLoggerConfig() { 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 static class PropertyChangeListenerImpl implements PropertyChangeListener { @Override @SuppressWarnings("synthetic-access") 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)) { configureFastLoggerDelegating(); } } } }