/*
* 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.log4j;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.geode.internal.logging.LogConfig;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.logging.ManagerLogWriter;
import org.apache.geode.internal.logging.PureLogWriter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.layout.PatternLayout;
/**
* A Log4j Appender which will copy all output to a LogWriter.
*
*/
public class LogWriterAppender extends AbstractAppender implements PropertyChangeListener {
private static final org.apache.logging.log4j.Logger logger = LogService.getLogger();
/** Is this thread in the process of appending? */
private static final ThreadLocal<Boolean> appending = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
private final PureLogWriter logWriter;
private final FileOutputStream fos; // TODO:LOG:CLEANUP: why do we track this outside
// ManagerLogWriter? doesn't rolling invalidate it?
private final AppenderContext[] appenderContexts;
private final String appenderName;
private final String logWriterLoggerName;
private LogWriterAppender(final AppenderContext[] appenderContexts, final String name,
final PureLogWriter logWriter, final FileOutputStream fos) {
super(LogWriterAppender.class.getName() + "-" + name, null,
PatternLayout.createDefaultLayout());
this.appenderContexts = appenderContexts;
this.appenderName = LogWriterAppender.class.getName() + "-" + name;
this.logWriterLoggerName = name;
this.logWriter = logWriter;
this.fos = fos;
}
/**
* Used by LogWriterAppenders and tests to create a new instance.
*
* @return The new instance.
*/
static final LogWriterAppender create(final AppenderContext[] contexts, final String name,
final PureLogWriter logWriter, final FileOutputStream fos) {
LogWriterAppender appender = new LogWriterAppender(contexts, name, logWriter, fos);
for (AppenderContext context : appender.appenderContexts) {
context.getLoggerContext().addPropertyChangeListener(appender);
}
appender.start();
for (AppenderContext context : appender.appenderContexts) {
context.getLoggerConfig().addAppender(appender, Level.ALL, null);
}
return appender;
}
@Override
public void append(final LogEvent event) {
// If already appending then don't send to avoid infinite recursion
if ((appending.get())) {
return;
}
appending.set(Boolean.TRUE);
try {
this.logWriter.put(LogWriterLogger.log4jLevelToLogWriterLevel(event.getLevel()),
event.getMessage().getFormattedMessage(), event.getThrown());
} finally {
appending.set(Boolean.FALSE);
}
}
@Override
public synchronized void propertyChange(final PropertyChangeEvent evt) {
if (logger.isDebugEnabled()) {
logger.debug("Responding to a property change event. Property name is {}.",
evt.getPropertyName());
}
if (evt.getPropertyName().equals(LoggerContext.PROPERTY_CONFIG)) {
for (AppenderContext context : this.appenderContexts) {
LoggerConfig loggerConfig = context.getLoggerConfig();
if (!loggerConfig.getAppenders().containsKey(this.appenderName)) {
loggerConfig.addAppender(this, Level.ALL, null);
}
}
}
}
/**
* Stop the appender and remove it from the Log4j configuration.
*/
protected void destroy() { // called 1st during disconnect
// add stdout appender to MAIN_LOGGER_NAME only if isUsingGemFireDefaultConfig -- see #51819
if (LogService.MAIN_LOGGER_NAME.equals(this.logWriterLoggerName)
&& LogService.isUsingGemFireDefaultConfig()) {
LogService.restoreConsoleAppender();
}
for (AppenderContext context : this.appenderContexts) {
context.getLoggerContext().removePropertyChangeListener(this);
context.getLoggerConfig().removeAppender(appenderName);
}
for (AppenderContext context : this.appenderContexts) { // do this second as log4j 2.6+ will
// re-add
context.getLoggerContext().updateLoggers();
}
stop();
cleanUp(); // 3rd
if (logger.isDebugEnabled()) {
logger.debug("A LogWriterAppender has been destroyed and cleanup is finished.");
}
}
private void cleanUp() { // was closingLogFile() -- called from destroy() as the final step
if (this.logWriter instanceof ManagerLogWriter) {
((ManagerLogWriter) this.logWriter).closingLogFile();
}
if (this.fos != null) {
try {
this.fos.close();
} catch (IOException ignore) {
}
}
}
@Override
public void stop() {
try {
if (this.logWriter instanceof ManagerLogWriter) {
((ManagerLogWriter) this.logWriter).shuttingDown();
}
} catch (RuntimeException e) {
logger.warn("RuntimeException encountered while shuttingDown LogWriterAppender", e);
}
super.stop();
}
protected void startupComplete() {
if (this.logWriter instanceof ManagerLogWriter) {
((ManagerLogWriter) this.logWriter).startupComplete();
}
}
protected void setConfig(final LogConfig cfg) {
if (this.logWriter instanceof ManagerLogWriter) {
((ManagerLogWriter) this.logWriter).setConfig(cfg);
}
}
public File getChildLogFile() {
if (this.logWriter instanceof ManagerLogWriter) {
return ((ManagerLogWriter) this.logWriter).getChildLogFile();
} else {
return null;
}
}
public File getLogDir() {
if (this.logWriter instanceof ManagerLogWriter) {
return ((ManagerLogWriter) this.logWriter).getLogDir();
} else {
return null;
}
}
public int getMainLogId() {
if (this.logWriter instanceof ManagerLogWriter) {
return ((ManagerLogWriter) this.logWriter).getMainLogId();
} else {
return -1;
}
}
public boolean useChildLogging() {
if (this.logWriter instanceof ManagerLogWriter) {
return ((ManagerLogWriter) this.logWriter).useChildLogging();
} else {
return false;
}
}
protected void configChanged() {
if (this.logWriter instanceof ManagerLogWriter) {
((ManagerLogWriter) this.logWriter).configChanged();
}
}
}