blob: d5a2ad8cc544023fed3ed723c1b2ddbe130b4bda [file] [log] [blame]
/*
* 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.logging.log4j.internal.impl;
import static org.apache.geode.logging.internal.log4j.LogWriterLevelConverter.toLevel;
import java.util.Collection;
import java.util.HashSet;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.filter.Filterable;
import org.apache.logging.log4j.core.lookup.StrLookup;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.geode.annotations.VisibleForTesting;
import org.apache.geode.logging.internal.log4j.api.FastLogger;
import org.apache.geode.logging.internal.spi.LogConfig;
import org.apache.geode.logging.internal.spi.LogLevelUpdateOccurs;
import org.apache.geode.logging.internal.spi.LogLevelUpdateScope;
import org.apache.geode.logging.internal.spi.LogWriterLevel;
import org.apache.geode.logging.internal.spi.LoggingProvider;
/**
* Log4J 2 implementation of {@link LoggingProvider}.
*
* <p>
* Returned Logger is wrapped inside an instance of {@link FastLogger} which skips expensive
* filtering, debug and trace handling with a volatile boolean. This optimization is turned on only
* when using the default Geode {@code log4j2.xml} by checking for the existence of this property:
*
* <pre>
* &lt;Property name="geode-default"&gt;true&lt;/Property&gt;
* </pre>
*
* <p>
* Returned Logger uses {@code GemFireParameterizedMessageFactory} to create
* {@code GemFireParameterizedMessage} which excludes {@code Region}s from being handled as a
* {@code Map} and {@code EntriesCollection} from being handled as a {@code Collection}. Without
* this change, using a {@code Region} or {@code EntriesCollection} in a log statement can result in
* an expensive operation or even a hang in the case of a {@code PartitionedRegion}.
*/
public class Log4jLoggingProvider implements LoggingProvider {
static final String GEODE_CONSOLE_APPENDER_NAME = "STDOUT";
static final String LOGWRITER_APPENDER_NAME = "LOGWRITER";
static final String SECURITY_LOGWRITER_APPENDER_NAME = "SECURITYLOGWRITER";
private static final String GEODE_VERBOSE_FILTER = "{GEODE_VERBOSE}";
private static final String GEMFIRE_VERBOSE_FILTER = "{GEMFIRE_VERBOSE}";
/**
* 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 GEODE_DEFAULT_PROPERTY = "geode-default";
public static void updateLogLevel(final Level level, final LoggerConfig... loggerConfigs) {
for (LoggerConfig loggerConfig : loggerConfigs) {
loggerConfig.setLevel(level);
}
getRootLoggerContext().updateLoggers();
}
public static LoggerConfig getLoggerConfig(final org.apache.logging.log4j.Logger logger) {
return ((Logger) logger).get();
}
@VisibleForTesting
static String getConfigurationInfoString() {
return getConfiguration().getConfigurationSource().toString();
}
static boolean isUsingGemFireDefaultConfig() {
Configuration configuration = getConfiguration();
StrSubstitutor strSubstitutor = configuration.getStrSubstitutor();
StrLookup variableResolver = strSubstitutor.getVariableResolver();
String value = variableResolver.lookup(GEODE_DEFAULT_PROPERTY);
return "true".equals(value);
}
private static LoggerContext getRootLoggerContext() {
return ((Logger) LogManager.getRootLogger()).getContext();
}
private static Configuration getConfiguration() {
return getRootLoggerContext().getConfiguration();
}
private boolean configuredSecurityAppenders;
public Log4jLoggingProvider() {
// nothing
}
@Override
public void configure(final LogConfig logConfig, final LogLevelUpdateOccurs logLevelUpdateOccurs,
final LogLevelUpdateScope logLevelUpdateScope) {
if (shouldUpdateLogLevels(logLevelUpdateOccurs)) {
Level loggerLevel = toLevel(LogWriterLevel.find(logConfig.getLogLevel()));
updateLogLevel(loggerLevel, getLoggerConfig(MAIN_LOGGER_NAME));
Level securityLoggerLevel = toLevel(LogWriterLevel.find(logConfig.getSecurityLogLevel()));
updateLogLevel(securityLoggerLevel, getLoggerConfig(SECURITY_LOGGER_NAME));
if (!LogConfig.hasSecurityLogFile(logConfig)) {
configuredSecurityAppenders =
configureSecurityAppenders(SECURITY_LOGGER_NAME, securityLoggerLevel);
}
}
if (shouldUpdateLogLevels(logLevelUpdateOccurs)) {
updateLogLevel(logConfig, logLevelUpdateScope);
}
configureFastLoggerDelegating();
}
private boolean shouldUpdateLogLevels(final LogLevelUpdateOccurs logLevelUpdateOccurs) {
return logLevelUpdateOccurs.always() ||
logLevelUpdateOccurs.onlyWhenUsingDefaultConfig() && isUsingGemFireDefaultConfig();
}
@Override
public void cleanup() {
if (configuredSecurityAppenders) {
Configuration log4jConfiguration = getRootLoggerContext().getConfiguration();
LoggerConfig loggerConfig = log4jConfiguration.getLoggerConfig(
SECURITY_LOGGER_NAME);
loggerConfig.removeAppender(GEODE_CONSOLE_APPENDER_NAME);
loggerConfig.removeAppender(LOGWRITER_APPENDER_NAME);
loggerConfig.setAdditive(false);
getRootLoggerContext().updateLoggers();
}
}
@Override
public String getConfigurationInfo() {
return getConfiguration().getConfigurationSource().toString();
}
@Override
public void enableLoggingToStandardOutput() {
Configuration log4jConfiguration = getRootLoggerContext().getConfiguration();
Appender appender = log4jConfiguration.getAppender(GEODE_CONSOLE_APPENDER_NAME);
if (appender instanceof PausableAppender) {
PausableAppender geodeConsoleAppender = (PausableAppender) appender;
geodeConsoleAppender.resume();
}
}
@Override
public void disableLoggingToStandardOutput() {
Configuration log4jConfiguration = getRootLoggerContext().getConfiguration();
Appender appender = log4jConfiguration.getAppender(GEODE_CONSOLE_APPENDER_NAME);
if (appender instanceof PausableAppender) {
PausableAppender geodeConsoleAppender = (PausableAppender) appender;
geodeConsoleAppender.pause();
}
}
@Override
public int getPriority() {
return 0;
}
@Override
public String toString() {
return new StringBuilder().append(super.toString()).append(": {configuredSecurityAppenders=")
.append(configuredSecurityAppenders).append("}").toString();
}
private void updateLogLevel(final LogConfig logConfig,
final LogLevelUpdateScope logLevelUpdateScope) {
Level level = toLevel(LogWriterLevel.find(logConfig.getLogLevel()));
Configuration configuration =
getRootLoggerContext().getConfiguration();
Collection<LoggerConfig> loggerConfigs = new HashSet<>();
for (LoggerConfig loggerConfig : configuration.getLoggers().values()) {
switch (logLevelUpdateScope) {
case ALL_LOGGERS:
loggerConfigs.add(loggerConfig);
break;
case GEODE_AND_SECURITY_LOGGERS:
if (loggerConfig.getName().startsWith(GEODE_LOGGER_PREFIX)) {
loggerConfigs.add(loggerConfig);
}
break;
case GEODE_AND_APPLICATION_LOGGERS:
if (!loggerConfig.getName().equals(SECURITY_LOGGER_NAME)) {
loggerConfigs.add(loggerConfig);
}
break;
case GEODE_LOGGERS:
if (loggerConfig.getName().startsWith(GEODE_LOGGER_PREFIX) &&
!loggerConfig.getName().equals(SECURITY_LOGGER_NAME)) {
loggerConfigs.add(loggerConfig);
}
}
}
updateLogLevel(level, loggerConfigs.toArray(new LoggerConfig[0]));
}
private boolean configureSecurityAppenders(final String name, final Level level) {
Configuration log4jConfiguration = getRootLoggerContext().getConfiguration();
LoggerConfig loggerConfig = log4jConfiguration.getLoggerConfig(name);
if (!loggerConfig.getName().equals(SECURITY_LOGGER_NAME)) {
return false;
}
Appender stdoutAppender = log4jConfiguration.getAppender(GEODE_CONSOLE_APPENDER_NAME);
Appender mainLogWriterAppender = log4jConfiguration.getAppender(LOGWRITER_APPENDER_NAME);
if (stdoutAppender != null) {
loggerConfig.addAppender(stdoutAppender, level, null);
}
if (mainLogWriterAppender != null) {
loggerConfig.addAppender(mainLogWriterAppender, level, null);
}
loggerConfig.setAdditive(true);
getRootLoggerContext().updateLoggers();
return true;
}
private LoggerConfig getLoggerConfig(final String name) {
Configuration log4jConfiguration = getRootLoggerContext().getConfiguration();
return log4jConfiguration.getLoggerConfig(name);
}
private void configureFastLoggerDelegating() {
Configuration configuration = getConfiguration();
if (hasContextWideFilter(configuration) || hasAppenderFilter(configuration)
|| hasDebugOrLower(configuration) || hasLoggerFilter(configuration)
|| hasAppenderRefFilter(configuration)) {
FastLogger.setDelegating(true);
} else {
FastLogger.setDelegating(false);
}
}
private boolean hasContextWideFilter(final Configuration config) {
return config.hasFilter();
}
private boolean hasAppenderFilter(final Configuration config) {
for (Appender appender : config.getAppenders().values()) {
if (appender instanceof AbstractFilterable) {
if (((Filterable) appender).hasFilter()) {
return true;
}
}
}
return false;
}
private boolean hasDebugOrLower(final Configuration config) {
for (LoggerConfig loggerConfig : config.getLoggers().values()) {
boolean isDebugOrLower = loggerConfig.getLevel().isLessSpecificThan(Level.DEBUG);
if (isDebugOrLower) {
return true;
}
}
return false;
}
private boolean hasLoggerFilter(final Configuration config) {
for (LoggerConfig loggerConfig : config.getLoggers().values()) {
boolean isRoot = loggerConfig.getName().isEmpty();
boolean isGemFire = loggerConfig.getName().startsWith(GEODE_LOGGER_PREFIX);
boolean hasFilter = loggerConfig.hasFilter();
boolean isGemFireVerboseFilter =
hasFilter && (GEODE_VERBOSE_FILTER.equals(loggerConfig.getFilter().toString())
|| GEMFIRE_VERBOSE_FILTER.equals(loggerConfig.getFilter().toString()));
if (isRoot || isGemFire) {
// check for Logger Filter
if (hasFilter && !isGemFireVerboseFilter) {
return true;
}
}
}
return false;
}
private boolean hasAppenderRefFilter(final Configuration config) {
for (LoggerConfig loggerConfig : config.getLoggers().values()) {
boolean isRoot = loggerConfig.getName().isEmpty();
boolean isGemFire = loggerConfig.getName().startsWith(GEODE_LOGGER_PREFIX);
if (isRoot || isGemFire) {
// check for AppenderRef Filter
for (AppenderRef appenderRef : loggerConfig.getAppenderRefs()) {
if (appenderRef.getFilter() != null) {
return true;
}
}
}
}
return false;
}
}