| /* |
| * 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()); |
| } |
| } |
| } |
| } |