blob: b9ac49efd9965e434cb7628e965452842c554d85 [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 java.lang.Boolean.FALSE;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.geode.internal.logging.ManagerLogWriter;
import org.apache.geode.internal.logging.ManagerLogWriterFactory;
import org.apache.geode.internal.logging.ManagerLogWriterFactory.LogFileRolloverDetails;
import org.apache.geode.internal.statistics.StatisticsConfig;
import org.apache.geode.logging.internal.LoggingSessionRegistryProvider;
import org.apache.geode.logging.internal.spi.LogConfig;
import org.apache.geode.logging.internal.spi.LogConfigListener;
import org.apache.geode.logging.internal.spi.LogConfigSupplier;
import org.apache.geode.logging.internal.spi.LogFile;
import org.apache.geode.logging.internal.spi.LoggingSessionListener;
import org.apache.geode.logging.internal.spi.LoggingSessionRegistry;
import org.apache.geode.logging.internal.spi.SessionContext;
@Plugin(name = LogWriterAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME,
elementType = Appender.ELEMENT_TYPE, printObject = true)
@SuppressWarnings("unused")
public class LogWriterAppender extends AbstractAppender
implements PausableAppender, DebuggableAppender, LoggingSessionListener, LogConfigListener {
public static final String PLUGIN_NAME = "GeodeLogWriter";
private static final boolean START_PAUSED_BY_DEFAULT = true;
/**
* True if this thread is in the process of appending.
*/
private static final ThreadLocal<Boolean> APPENDING = ThreadLocal.withInitial(() -> FALSE);
private final String eagerMemberName;
private volatile String lazyMemberName;
private final MemberNameSupplier memberNameSupplier;
/**
* appendLog used to be controlled by undocumented system property gemfire.append-log
*/
private final boolean appendLog;
private final boolean security;
private final boolean debug;
private final List<LogEvent> events;
private final LoggingSessionRegistry loggingSessionRegistry;
private volatile ManagerLogWriter logWriter;
private volatile LogConfigSupplier logConfigSupplier;
private volatile LogFileRolloverDetails logFileRolloverDetails;
private volatile boolean paused;
protected LogWriterAppender(final String name,
final Layout<? extends Serializable> layout,
final Filter filter,
final ManagerLogWriter logWriter) {
this(name, layout, filter, MemberNamePatternConverter.INSTANCE.getMemberNameSupplier(), null,
true, false, START_PAUSED_BY_DEFAULT, false, LoggingSessionRegistryProvider.get());
}
protected LogWriterAppender(final String name,
final Layout<? extends Serializable> layout,
final Filter filter,
final MemberNameSupplier memberNameSupplier,
final String eagerMemberName,
final boolean appendLog,
final boolean security,
final boolean startPaused,
final boolean debug,
final LoggingSessionRegistry loggingSessionRegistry) {
super(name, filter, layout);
this.memberNameSupplier = memberNameSupplier;
if (eagerMemberName != null) {
memberNameSupplier.set(eagerMemberName);
}
this.eagerMemberName = eagerMemberName;
this.appendLog = appendLog;
this.security = security;
this.debug = debug;
if (debug) {
events = Collections.synchronizedList(new ArrayList<>());
} else {
events = Collections.emptyList();
}
this.loggingSessionRegistry = loggingSessionRegistry;
paused = startPaused;
}
@PluginBuilderFactory
public static <B extends LogWriterAppender.Builder<B>> B newBuilder() {
return new LogWriterAppender.Builder<B>().asBuilder();
}
/**
* Builds LogWriterAppender instances.
*
* @param <B> The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
implements org.apache.logging.log4j.core.util.Builder<LogWriterAppender> {
@PluginBuilderAttribute
private String memberName;
@PluginBuilderAttribute
private boolean security;
@PluginBuilderAttribute
private boolean appendLog = true;
@PluginBuilderAttribute
private boolean startPaused = START_PAUSED_BY_DEFAULT;
@PluginBuilderAttribute
private boolean debug;
// GEODE-5785: add file permissions support similar to FileAppender
public B withMemberName(final String memberName) {
this.memberName = memberName;
return asBuilder();
}
public String getMemberName() {
return memberName;
}
public B setSecurity(final boolean security) {
this.security = security;
return asBuilder();
}
public boolean isSecurity() {
return security;
}
public B setAppendLog(final boolean shouldAppendLog) {
appendLog = shouldAppendLog;
return asBuilder();
}
public boolean isAppendLog() {
return appendLog;
}
public B setStartPaused(final boolean shouldStartPaused) {
startPaused = shouldStartPaused;
return asBuilder();
}
public boolean isStartPaused() {
return debug;
}
public B setDebug(final boolean shouldDebug) {
debug = shouldDebug;
return asBuilder();
}
public boolean isDebug() {
return debug;
}
@Override
public LogWriterAppender build() {
Layout<? extends Serializable> layout = getOrCreateLayout();
return new LogWriterAppender(getName(), layout, getFilter(),
MemberNamePatternConverter.INSTANCE.getMemberNameSupplier(), memberName, appendLog,
security, startPaused, debug, LoggingSessionRegistryProvider.get());
}
}
@Override
public void append(final LogEvent event) {
if (isPaused()) {
return;
}
doAppendIfNotAppending(event);
}
@Override
public void start() {
LOGGER.info("Starting {}.", this);
LOGGER.debug("Adding {} to {}.", this, loggingSessionRegistry);
loggingSessionRegistry.addLoggingSessionListener(this);
super.start();
}
@Override
public void stop() {
LOGGER.info("Stopping {}.", this);
// stop LogEvents from coming to this appender
super.stop();
// clean up
loggingSessionRegistry.removeLoggingSessionListener(this);
stopSession();
LOGGER.info("{} has stopped.", this);
}
@Override
public void pause() {
LOGGER.debug("Pausing {}.", this);
paused = true;
}
@Override
public void resume() {
LOGGER.debug("Resuming {}.", this);
paused = false;
}
@Override
public boolean isPaused() {
return paused;
}
@Override
public void clearLogEvents() {
events.clear();
}
@Override
public List<LogEvent> getLogEvents() {
return events;
}
@Override
public synchronized void createSession(final SessionContext sessionContext) {
logConfigSupplier = sessionContext.getLogConfigSupplier();
LOGGER.info("Creating session in {} with {}.", this, logConfigSupplier);
logConfigSupplier.addLogConfigListener(this);
LogConfig logConfig = logConfigSupplier.getLogConfig();
if (eagerMemberName == null && lazyMemberName == null) {
String memberName = logConfig.getName();
memberNameSupplier.set(memberName);
lazyMemberName = memberName;
}
StatisticsConfig statisticsConfig = logConfigSupplier.getStatisticsConfig();
ManagerLogWriterFactory managerLogWriterFactory = new ManagerLogWriterFactory()
.setSecurity(security).setAppendLog(appendLog);
logWriter = managerLogWriterFactory.create(logConfig, statisticsConfig);
if (logWriter == null) {
logWriter = new NullLogWriter();
}
logFileRolloverDetails = managerLogWriterFactory.getLogFileRolloverDetails();
}
@Override
public synchronized void startSession() {
LOGGER.info("Starting session in {}.", this);
logWriter.startupComplete();
resume();
logRolloverDetails(logFileRolloverDetails);
logFileRolloverDetails = null;
logConfigSupplier = null;
}
@Override
public synchronized void stopSession() {
LOGGER.info("Stopping session in {}.", this);
logWriter.shuttingDown();
pause();
logWriter.closingLogFile();
}
@Override
public Optional<LogFile> getLogFile() {
return Optional.of(new LogFile(logWriter));
}
@Override
public void configChanged() {
logWriter.configChanged();
}
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode()) + ":" + getName()
+ " {eagerMemberName=" + eagerMemberName + ", lazyMemberName=" + lazyMemberName
+ "appendLog=" + appendLog + ", security=" + security + ", paused=" + paused
+ ", loggingSessionRegistry=" + loggingSessionRegistry + ", logWriter=" + logWriter
+ ", debug=" + debug + "}";
}
ManagerLogWriter getLogWriter() {
return logWriter;
}
private void doAppendIfNotAppending(final LogEvent event) {
if (APPENDING.get()) {
// If already appending then don't send to avoid infinite recursion
return;
}
APPENDING.set(Boolean.TRUE);
try {
ManagerLogWriter currentLogWriter = logWriter;
if (currentLogWriter == null || currentLogWriter instanceof NullLogWriter) {
return;
}
doAppendToLogWriter(currentLogWriter, event);
} finally {
APPENDING.set(FALSE);
}
}
private void doAppendToLogWriter(final ManagerLogWriter logWriter, final LogEvent event) {
byte[] bytes = getLayout().toByteArray(event);
if (bytes != null && bytes.length > 0) {
logWriter.writeFormattedMessage(new String(bytes, Charset.defaultCharset()));
}
if (debug) {
events.add(event);
}
}
private void logRolloverDetails(final LogFileRolloverDetails logFileRolloverDetails) {
// log the first msg about renaming logFile for rolling if it pre-existed
if (logFileRolloverDetails.exists()) {
if (logFileRolloverDetails.isWarning()) {
LogManager.getLogger().warn(logFileRolloverDetails.getMessage());
} else {
LogManager.getLogger().info(logFileRolloverDetails.getMessage());
}
}
}
}